001/* 002 * Copyright 2014 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.contenttype; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Optional; 031import java.util.Set; 032import java.util.TreeSet; 033import java.util.function.Function; 034import java.util.stream.Collectors; 035 036import org.apache.avalon.framework.activity.Disposable; 037import org.apache.avalon.framework.activity.Initializable; 038import org.apache.avalon.framework.component.Component; 039import org.apache.avalon.framework.logger.AbstractLogEnabled; 040import org.apache.avalon.framework.service.ServiceException; 041import org.apache.avalon.framework.service.ServiceManager; 042import org.apache.avalon.framework.service.Serviceable; 043import org.apache.avalon.framework.thread.ThreadSafe; 044import org.apache.cocoon.ProcessingException; 045import org.apache.commons.lang3.ArrayUtils; 046import org.apache.commons.lang3.StringUtils; 047import org.apache.commons.lang3.tuple.Pair; 048 049import org.ametys.cms.content.RootContentHelper; 050import org.ametys.cms.data.type.ModelItemTypeExtensionPoint; 051import org.ametys.cms.repository.Content; 052import org.ametys.core.cache.AbstractCacheManager; 053import org.ametys.core.cache.Cache; 054import org.ametys.core.right.RightManager; 055import org.ametys.core.right.RightManager.RightResult; 056import org.ametys.core.ui.Callable; 057import org.ametys.core.user.CurrentUserProvider; 058import org.ametys.core.user.UserIdentity; 059import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 060import org.ametys.runtime.i18n.I18nizableText; 061import org.ametys.runtime.model.DefinitionContext; 062import org.ametys.runtime.model.ElementDefinition; 063import org.ametys.runtime.model.ModelHelper; 064import org.ametys.runtime.model.ModelHelper.ConfigurationAndPluginName; 065import org.ametys.runtime.model.ModelItem; 066import org.ametys.runtime.model.ModelViewItem; 067import org.ametys.runtime.model.View; 068import org.ametys.runtime.model.ViewElement; 069import org.ametys.runtime.model.ViewItem; 070import org.ametys.runtime.model.ViewItemContainer; 071import org.ametys.runtime.model.exception.UndefinedItemPathException; 072import org.ametys.runtime.model.type.ModelItemTypeConstants; 073 074/** 075 * Helper for manipulating {@link ContentType}s 076 */ 077public class ContentTypesHelper extends AbstractLogEnabled implements Component, Serviceable, ThreadSafe, Disposable, Initializable 078{ 079 /** The Avalon role */ 080 public static final String ROLE = ContentTypesHelper.class.getName(); 081 082 /** Archived content type */ 083 public static final String ARCHIVED_CONTENT_TYPE = "org.ametys.cms.ArchivedContent"; 084 085 private static final String __VIEW_METADATASET = ContentTypesHelper.class.getName() + "$view.metadataset"; 086 087 private static final String __EDITION_METADATASET = ContentTypesHelper.class.getName() + "$edition.metadataset"; 088 089 private static final String __VIEW_CACHE = ContentTypesHelper.class.getName() + "$view.cache"; 090 091 /** The content types extension point */ 092 protected ContentTypeExtensionPoint _cTypeEP; 093 /** The current user provider */ 094 protected CurrentUserProvider _userProvider; 095 /** The rights manager */ 096 protected RightManager _rightManager; 097 /** Helper for root content */ 098 protected RootContentHelper _rootContentHelper; 099 /** The extension point with the available types for contents */ 100 protected ModelItemTypeExtensionPoint _contentAttributeTypeExtensionPoint; 101 102 private DynamicContentTypeDescriptorExtentionPoint _dynamicCTDescriptorEP; 103 104 private ServiceManager _smanager; 105 106 private AbstractCacheManager _cacheManager; 107 108 @Override 109 public void service(ServiceManager smanager) throws ServiceException 110 { 111 _smanager = smanager; 112 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 113 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 114 _rootContentHelper = (RootContentHelper) smanager.lookup(RootContentHelper.ROLE); 115 _contentAttributeTypeExtensionPoint = (ModelItemTypeExtensionPoint) smanager.lookup(ModelItemTypeExtensionPoint.ROLE_CONTENT_ATTRIBUTE); 116 _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE); 117 } 118 119 /** 120 * Initialize 121 */ 122 @Override 123 public void initialize() 124 { 125 _createCaches(); 126 } 127 128 /** 129 * Creates the caches 130 */ 131 protected void _createCaches() 132 { 133 _cacheManager.createMemoryCache(__VIEW_METADATASET, 134 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_METADATASET_LABEL"), 135 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_METADATASET_DESCRIPTION"), 136 true, 137 null); 138 _cacheManager.createMemoryCache(__EDITION_METADATASET, 139 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_EDITION_METADATASET_LABEL"), 140 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_EDITION_METADATASET_DESCRIPTION"), 141 true, 142 null); 143 _cacheManager.createMemoryCache(__VIEW_CACHE, 144 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_LABEL"), 145 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_DESCRIPTION"), 146 true, 147 null); 148 } 149 150 private DynamicContentTypeDescriptorExtentionPoint _getDynamicContentTypeDescriptorExtentionPoint() 151 { 152 if (_dynamicCTDescriptorEP == null) 153 { 154 try 155 { 156 _dynamicCTDescriptorEP = (DynamicContentTypeDescriptorExtentionPoint) _smanager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE); 157 } 158 catch (ServiceException e) 159 { 160 throw new IllegalStateException(e); 161 } 162 } 163 return _dynamicCTDescriptorEP; 164 } 165 166 @Override 167 public void dispose() 168 { 169 _getViewCache().invalidateAll(); 170 } 171 172 /** 173 * Lazy lookup of {@link ContentTypeExtensionPoint} 174 * @return the content type extension point 175 */ 176 protected ContentTypeExtensionPoint _getContentTypeEP() 177 { 178 if (_cTypeEP == null) 179 { 180 try 181 { 182 _cTypeEP = (ContentTypeExtensionPoint) _smanager.lookup(ContentTypeExtensionPoint.ROLE); 183 } 184 catch (ServiceException e) 185 { 186 throw new RuntimeException("Unable to lookup ContentTypeExtensionPoint component", e); 187 } 188 } 189 return _cTypeEP; 190 } 191 192 /** 193 * Determines if a content is a instance of given content type id 194 * 195 * @param content The content 196 * @param cTypeId The id of content type or mixin 197 * @return <code>true</code> if the content is an instance of content type 198 */ 199 public boolean isInstanceOf(Content content, String cTypeId) 200 { 201 String[] types = content.getTypes(); 202 if (ArrayUtils.contains(types, cTypeId)) 203 { 204 return true; 205 } 206 207 String[] mixins = content.getMixinTypes(); 208 if (ArrayUtils.contains(mixins, cTypeId)) 209 { 210 return true; 211 } 212 213 return _containsContentType(ArrayUtils.addAll(types, mixins), cTypeId); 214 } 215 216 private boolean _containsContentType(String[] cTypesId, String cTypeId) 217 { 218 for (String id : cTypesId) 219 { 220 ContentType cType = _getContentTypeEP().getExtension(id); 221 if (cType != null) 222 { 223 if (ArrayUtils.contains(cType.getSupertypeIds(), cTypeId)) 224 { 225 return true; 226 } 227 else if (_containsContentType(cType.getSupertypeIds(), cTypeId)) 228 { 229 return true; 230 } 231 } 232 } 233 return false; 234 } 235 236 /** 237 * Get the identifiers of the content types common ancestors 238 * @param contentTypeIds The identifiers of the content types to compare 239 * @return The identifiers of common ancestors 240 */ 241 public Set<String> getCommonAncestors(Collection<String> contentTypeIds) 242 { 243 Set<String> commonAncestors = new HashSet<>(); 244 245 // Get ancestors of each content type 246 List<Collection<String>> superTypeIdsByCType = new ArrayList<>(); 247 for (String contentTypeId : contentTypeIds) 248 { 249 Set<String> superTypeIds = new HashSet<>(); 250 251 superTypeIds.add(contentTypeId); 252 superTypeIds.addAll(getAncestors(contentTypeId)); 253 254 superTypeIdsByCType.add(superTypeIds); 255 } 256 257 // Make the intersection of all the ancestors collections 258 if (!superTypeIdsByCType.isEmpty()) 259 { 260 Iterator<Collection<String>> superTypeIdsByCTypeIt = superTypeIdsByCType.iterator(); 261 commonAncestors.addAll(superTypeIdsByCTypeIt.next()); 262 while (superTypeIdsByCTypeIt.hasNext() && !commonAncestors.isEmpty()) 263 { 264 commonAncestors.retainAll(superTypeIdsByCTypeIt.next()); 265 } 266 } 267 268 // Remove ancestors of ancestors: their attributes will be given by the first one 269 commonAncestors = removeAncestors(commonAncestors); 270 271 return commonAncestors; 272 } 273 274 /** 275 * Remove all content types in the set that are ancestors of other content types in the set. 276 * @param contentTypes a Set of content type IDs. 277 * @return a Set of content type IDs without ancestors. 278 */ 279 protected Set<String> removeAncestors(Set<String> contentTypes) 280 { 281 Set<String> noAncestors = new HashSet<>(contentTypes); 282 283 Iterator<String> it1 = contentTypes.iterator(); 284 while (it1.hasNext()) 285 { 286 ContentType cType1 = _getContentTypeEP().getExtension(it1.next()); 287 288 Iterator<String> it2 = contentTypes.iterator(); 289 while (it2.hasNext()) 290 { 291 String cType2 = it2.next(); 292 String[] supertypeIds = cType1.getSupertypeIds(); 293 294 // CType2 is an ancestor of CType1: remove it. 295 if (ArrayUtils.contains(supertypeIds, cType2)) 296 { 297 noAncestors.remove(cType2); 298 } 299 } 300 } 301 302 return noAncestors; 303 } 304 305 /** 306 * Get all ancestors for the given content type 307 * 308 * @param contentTypeId The content type id to test 309 * @return A non-null set of all ancestors. Does not contains the contentTypeId itself. 310 * @throws IllegalArgumentException if the content type does not exist. 311 */ 312 public Set<String> getAncestors(String contentTypeId) 313 { 314 Set<String> superTypes = new HashSet<>(); 315 316 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 317 318 if (cType == null) 319 { 320 throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'"); 321 } 322 323 324 String[] supertypeIds = cType.getSupertypeIds(); 325 326 for (String superTypeId : supertypeIds) 327 { 328 superTypes.add(superTypeId); 329 superTypes.addAll(getAncestors(superTypeId)); 330 } 331 332 return superTypes; 333 334 } 335 336 /** 337 * Get super type's ids for the given content type 338 * The first entry contains super content types, the second one contains super mixin types 339 * 340 * @param contentTypeId The content type id to test 341 * @return An array of super type's ids. 342 * @throws IllegalArgumentException if the content type does not exist. 343 */ 344 public Pair<String[], String[]> getSupertypeIds(String contentTypeId) 345 { 346 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 347 if (contentType == null) 348 { 349 throw new IllegalArgumentException("Unable to get super types of unknown content type '" + contentTypeId + "'"); 350 } 351 352 List<String> superMixins = new ArrayList<>(); 353 List<String> superContentTypes = new ArrayList<>(); 354 for (String supertypeId : contentType.getSupertypeIds()) 355 { 356 ContentType supertype = _getContentTypeEP().getExtension(supertypeId); 357 if (supertype == null) 358 { 359 throw new IllegalArgumentException("Unable to get the unknown super type '" + supertypeId + "' for type '" + contentTypeId + "'"); 360 } 361 362 if (supertype.isMixin()) 363 { 364 superMixins.add(supertypeId); 365 } 366 else 367 { 368 superContentTypes.add(supertypeId); 369 } 370 } 371 372 String[] superContentTypesArray = superContentTypes.toArray(new String[superContentTypes.size()]); 373 String[] superMixinsArray = superMixins.toArray(new String[superMixins.size()]); 374 return Pair.of(superContentTypesArray, superMixinsArray); 375 } 376 377 /** 378 * Get plugin name for the given content type 379 * 380 * @param contentTypeId The content type id to test 381 * @return the plugin name 382 * @throws IllegalArgumentException if the content type does not exist. 383 */ 384 public String getPluginName(String contentTypeId) 385 { 386 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 387 if (cType == null) 388 { 389 throw new IllegalArgumentException("Unable to get plugin name of unknown content type '" + contentTypeId + "'"); 390 } 391 392 return cType.getPluginName(); 393 } 394 395 /** 396 * Builds the reverse hierarchies of ancestors of a content type 397 * @param contentTypeId The content type's id 398 * @return the reverse hierarchies with ancestors 399 */ 400 public List<Set<String>> buildReverseHierarchies(String contentTypeId) 401 { 402 Set<String> hierarchy = new LinkedHashSet<>(); 403 hierarchy.add(contentTypeId); 404 return _buildReverseHierarchies (contentTypeId, hierarchy); 405 } 406 407 private List<Set<String>> _buildReverseHierarchies(String contentTypeId, Set<String> hierarchy) 408 { 409 List<Set<String>> hierarchies = new ArrayList<>(); 410 411 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 412 if (cType == null) 413 { 414 throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'"); 415 } 416 417 String[] supertypeIds = cType.getSupertypeIds(); 418 if (supertypeIds.length > 0) 419 { 420 for (String superTypeId : supertypeIds) 421 { 422 Set<String> superHierarchy = new LinkedHashSet<>(hierarchy); 423 superHierarchy.add(superTypeId); 424 hierarchies.addAll(_buildReverseHierarchies(superTypeId, superHierarchy)); 425 } 426 } 427 else 428 { 429 hierarchies.add(hierarchy); 430 } 431 432 return hierarchies; 433 } 434 435 /** 436 * Get all views resulting of the concatenation of views of given content types and mixins. 437 * @param contentTypeIds the identifiers of the content types 438 * @param mixinIds the identifiers of the mixins 439 * @return The views 440 */ 441 public Map<String, View> getViews(String[] contentTypeIds, String[] mixinIds) 442 { 443 return getViews(contentTypeIds, mixinIds, Collections.emptySet()); 444 } 445 446 /** 447 * Get all views resulting of the concatenation of views of given content types and mixins. 448 * @param contentTypeIds the identifiers of the content types 449 * @param mixinIds the identifiers of the mixins 450 * @param viewNamesToAvoid names of views that should not be managed 451 * @return The views 452 */ 453 public Map<String, View> getViews(String[] contentTypeIds, String[] mixinIds, Set<String> viewNamesToAvoid) 454 { 455 Map<String, View> views = new HashMap<>(); 456 457 for (String contentTypeId : contentTypeIds) 458 { 459 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 460 for (String viewName : contentType.getViewNames()) 461 { 462 if (!viewNamesToAvoid.contains(viewName) && !views.containsKey(viewName)) 463 { 464 views.put(viewName, getView(viewName, contentTypeIds, mixinIds)); 465 } 466 } 467 } 468 469 return views; 470 } 471 472 /** 473 * Get the view for view resulting of the concatenation of views of the given content. 474 * @param viewName the name of the view to retrieve 475 * @param content the given content 476 * @return The view or null if none matches. 477 */ 478 public View getView(String viewName, Content content) 479 { 480 return getView(viewName, content.getTypes(), content.getMixinTypes()); 481 } 482 483 /** 484 * Get the view for view resulting of the concatenation of views of the given content types and mixins. 485 * @param viewName the name of the view to retrieve 486 * @param contentTypeIds the identifiers of the content types 487 * @param mixinIds the identifiers of the mixins 488 * @return The view or null if none matches. 489 */ 490 public View getView(String viewName, String[] contentTypeIds, String[] mixinIds) 491 { 492 CacheKey cacheKey = CacheKey.of(Set.of(contentTypeIds), Set.of(mixinIds), viewName); 493 return _getViewCache().get(cacheKey, __ -> _computeView(viewName, contentTypeIds, mixinIds)); 494 } 495 496 /** 497 * Get the view for view resulting for a given content 498 * @param viewName the name of the view to retrieve. If null or empty, fallback view will be used. 499 * @param fallbackViewName the name of the view to retrieve if the initial was not found. If null or empty, "main" view view will be used as fallback view. 500 * @param content the content 501 * @return The view or null if none matches. 502 */ 503 public View getViewWithFallback(String viewName, String fallbackViewName, Content content) 504 { 505 return getViewWithFallback(viewName, fallbackViewName, content.getTypes(), content.getMixinTypes()); 506 } 507 508 /** 509 * Get the view for view resulting of the concatenation of views of the given content types and mixins. 510 * @param viewName the name of the view to retrieve. If null or empty, fallback view will be used. 511 * @param fallbackViewName the name of the view to retrieve if the initial was not found. If null or empty, "main" view view will be used as fallback view. 512 * @param contentTypeIds the identifiers of the content types 513 * @param mixinIds the identifiers of the mixins 514 * @return The view or null if none matches. 515 */ 516 public View getViewWithFallback(String viewName, String fallbackViewName, String[] contentTypeIds, String[] mixinIds) 517 { 518 String usedFallbackViewName = fallbackViewName; 519 if (StringUtils.isBlank(fallbackViewName)) 520 { 521 usedFallbackViewName = "main"; 522 } 523 524 // Use the fallbackViewName if no viewName is provided 525 String usedViewName = viewName; 526 if (StringUtils.isBlank(usedViewName)) 527 { 528 usedViewName = usedFallbackViewName; 529 } 530 531 View view = getView(usedViewName, contentTypeIds, mixinIds); 532 533 if (view == null && !usedViewName.equals(usedFallbackViewName)) 534 { 535 view = getView(usedFallbackViewName, contentTypeIds, mixinIds); 536 } 537 538 return view; 539 } 540 541 /** 542 * Converts the view with the given name in a JSON Map 543 * @param contentTypeId the content type identifier 544 * @param viewName the name of the view to convert 545 * @param isEdition <code>true</code> if the JSON result is used for edition purposes (configure a form panel, ...) <code>false</code> otherwise 546 * @return the view as a JSON Map 547 * @throws ProcessingException if an error occurs when converting the view 548 */ 549 @Callable 550 public Map<String, Object> getViewAsJSON(String contentTypeId, String viewName, boolean isEdition) throws ProcessingException 551 { 552 Map<String, Object> json = new LinkedHashMap<>(); 553 554 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 555 json.put("contentType", Map.of( 556 "id", contentTypeId, 557 "label", contentType.getLabel(), 558 "defaultTitle", contentType.getDefaultTitle())); 559 560 View view = contentType.getView(viewName); 561 if (view != null) 562 { 563 json.put("view", view.toJSON(DefinitionContext.newInstance().withEdition(isEdition))); 564 } 565 else 566 { 567 if (getLogger().isWarnEnabled()) 568 { 569 getLogger().warn(String.format("Unknown view '%s' for content type '%s'", viewName, contentType.getId())); 570 } 571 } 572 573 return json; 574 } 575 576 /** 577 * Converts the title view in a JSON Map 578 * @param contentTypeId the content type identifier 579 * @param isEdition <code>true</code> if the JSON result is used for edition purposes (configure a form panel, ...) <code>false</code> otherwise 580 * @return the view as a JSON Map 581 * @throws ProcessingException if an error occurs when converting the view 582 */ 583 @Callable 584 public Map<String, Object> getTitleViewAsJSON(String contentTypeId, boolean isEdition) throws ProcessingException 585 { 586 Map<String, Object> json = new LinkedHashMap<>(); 587 588 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 589 json.put("contentType", Map.of( 590 "id", contentTypeId, 591 "label", contentType.getLabel(), 592 "defaultTitle", contentType.getDefaultTitle())); 593 594 View titleView = new View(); 595 596 ModelItem modelItem = contentType.getModelItem("title"); 597 ViewElement viewElement = new ViewElement(); 598 viewElement.setDefinition((ElementDefinition) modelItem); 599 titleView.addViewItem(viewElement); 600 601 json.put("view", titleView.toJSON(DefinitionContext.newInstance().withEdition(isEdition))); 602 return json; 603 } 604 605 private View _computeView(String viewName, String[] contentTypeIds, String[] mixinIds) 606 { 607 List<View> views = new ArrayList<>(); 608 for (String contentTypeId : contentTypeIds) 609 { 610 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 611 612 View view = contentType.getView(viewName); 613 if (view != null) 614 { 615 views.add(view); 616 } 617 } 618 619 for (String id : mixinIds) 620 { 621 ContentType mixin = _getContentTypeEP().getExtension(id); 622 623 View view = mixin.getView(viewName); 624 if (view != null) 625 { 626 views.add(view); 627 } 628 } 629 630 return joinViews(views); 631 } 632 633 /** 634 * Creates a view that is a jointure between all the given views 635 * @param views the views to join 636 * @return the joined view 637 */ 638 public View joinViews(List<View> views) 639 { 640 if (views.isEmpty()) 641 { 642 return null; 643 } 644 645 View joinView = new View(); 646 for (View view : views) 647 { 648 joinView.includeView(view); 649 650 if (joinView.getName() == null) 651 { 652 joinView.setName(view.getName()); 653 joinView.setLabel(view.getLabel()); 654 joinView.setDescription(view.getDescription()); 655 joinView.setInternal(view.isInternal()); 656 joinView.setIconGlyph(view.getIconGlyph()); 657 joinView.setIconDecorator(view.getIconDecorator()); 658 joinView.setSmallIcon(view.getSmallIcon()); 659 joinView.setMediumIcon(view.getMediumIcon()); 660 joinView.setLargeIcon(view.getLargeIcon()); 661 } 662 } 663 return joinView; 664 } 665 666 /** 667 * Stores all the configurations of the current content type for a view 668 * @param viewName the name of the view 669 * @param mainConfiguration the main configuration of the view, if it is configures on the current type 670 * @param overrides the list of overrides configured on the current type 671 */ 672 public record ViewConfigurations(String viewName, Optional<ConfigurationAndPluginName> mainConfiguration, List<ConfigurationAndPluginName> overrides) { /* empty */ } 673 674 /** 675 * Stores all the configurations for a view, by the content types in which the configuration is declared 676 * @param viewName the name of the view 677 * @param mainConfigurations the main configurations of the view 678 * @param overrides the list of overrides of the view 679 */ 680 public record ViewConfigurationsByType(String viewName, Map<ContentType, ConfigurationAndPluginName> mainConfigurations, Map<ContentType, List<ConfigurationAndPluginName>> overrides) { /* empty */ } 681 682 /** 683 * Get all configurations of the views resulting of the concatenation of views of given content types and mixins. 684 * @param contentTypeIds the identifiers of the content types 685 * @param mixinIds the identifiers of the mixins 686 * @return The views' configurations, indexed by view names and content type containing the configuration 687 */ 688 public Map<String, ViewConfigurationsByType> getViewConfigurations(String[] contentTypeIds, String[] mixinIds) 689 { 690 Map<String, ViewConfigurationsByType> viewConfigurations = new HashMap<>(); 691 for (String viewName : _getConfiguredViewNames(contentTypeIds)) 692 { 693 getViewConfigurationsByType(viewName, contentTypeIds, mixinIds) 694 .ifPresent(currentViewConfigurations -> viewConfigurations.put(viewName, currentViewConfigurations)); 695 } 696 697 return viewConfigurations; 698 } 699 700 private Set<String> _getConfiguredViewNames(String[] contentTypeIds) 701 { 702 List<ContentType> contentTypes = Arrays.stream(contentTypeIds) 703 .map(_getContentTypeEP()::getExtension) 704 .collect(Collectors.toList()); 705 706 // Create a Set with configured views of the given content types 707 Set<String> viewNames = contentTypes.stream() 708 .map(ContentType::getViewConfigurations) 709 .map(Map::keySet) 710 .flatMap(Collection::stream) 711 .collect(Collectors.toSet()); 712 713 // Add views configured on given content types' super types 714 contentTypes.stream() 715 .map(ContentType::getSupertypeIds) 716 .map(this::_getConfiguredViewNames) 717 .forEach(viewNames::addAll); 718 719 return viewNames; 720 } 721 722 /** 723 * Get all configurations of the given view resulting of the concatenation of given content types and mixins. 724 * @param viewName the name of the view 725 * @param contentTypeIds the identifiers of the content types 726 * @param mixinIds the identifiers of the mixins 727 * @return The views' configurations, indexed by content type containing the configuration 728 */ 729 public Optional<ViewConfigurationsByType> getViewConfigurationsByType(String viewName, String[] contentTypeIds, String[] mixinIds) 730 { 731 Optional<ViewConfigurationsByType> viewConfigurationsByType = Optional.empty(); 732 for (String contentTypeId : contentTypeIds) 733 { 734 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 735 736 // If there is a main configuration for the view in this type, add the confs in the record but do not check the configurations of the super types 737 if (_hasMainConfiguration(contentType, viewName)) 738 { 739 ViewConfigurations currentTypeViewConfigurations = contentType.getViewConfigurations(viewName).get(); 740 // Main configuration 741 Map<ContentType, ConfigurationAndPluginName> mainConfigurations = viewConfigurationsByType.map(ViewConfigurationsByType::mainConfigurations) 742 .orElse(new LinkedHashMap<>()); 743 currentTypeViewConfigurations.mainConfiguration().ifPresent(config -> mainConfigurations.put(contentType, config)); 744 745 // overrides 746 Map<ContentType, List<ConfigurationAndPluginName>> overrides = viewConfigurationsByType.map(ViewConfigurationsByType::overrides) 747 .orElse(new LinkedHashMap<>()); 748 if (!currentTypeViewConfigurations.overrides().isEmpty()) 749 { 750 overrides.put(contentType, currentTypeViewConfigurations.overrides()); 751 } 752 753 viewConfigurationsByType = Optional.of(new ViewConfigurationsByType(viewName, mainConfigurations, overrides)); 754 } 755 else 756 { 757 Optional<ViewConfigurationsByType> parentsOptionalViewConfigurations = getViewConfigurationsByType(viewName, contentType.getSupertypeIds(), new String[0]); 758 if (parentsOptionalViewConfigurations.isPresent() && !parentsOptionalViewConfigurations.get().mainConfigurations().isEmpty()) 759 { 760 ViewConfigurationsByType parentsViewConfigurations = parentsOptionalViewConfigurations.get(); 761 762 // Merge parents main configurations to the current one 763 Map<ContentType, ConfigurationAndPluginName> mainConfigurations = parentsViewConfigurations.mainConfigurations(); 764 viewConfigurationsByType.map(ViewConfigurationsByType::mainConfigurations) 765 .ifPresent(mainConfigurations::putAll); 766 767 // Merge parents overrides to the current one. Put first the parent's overrides, the the child's one, and at the end the overrides that are already present 768 Map<ContentType, List<ConfigurationAndPluginName>> overrides = parentsViewConfigurations.overrides(); 769 if (_hasOverrides(contentType, viewName)) 770 { 771 ViewConfigurations currentTypeViewConfigurations = contentType.getViewConfigurations(viewName).get(); 772 overrides.put(contentType, currentTypeViewConfigurations.overrides()); 773 } 774 viewConfigurationsByType.map(ViewConfigurationsByType::overrides) 775 .ifPresent(overrides::putAll); 776 777 viewConfigurationsByType = Optional.of(new ViewConfigurationsByType(viewName, mainConfigurations, overrides)); 778 } 779 } 780 } 781 782 return viewConfigurationsByType; 783 } 784 785 private boolean _hasMainConfiguration(ContentType contentType, String viewName) 786 { 787 Optional<ViewConfigurations> optionalViewConfigurations = contentType.getViewConfigurations(viewName); 788 return optionalViewConfigurations.isPresent() && optionalViewConfigurations.get().mainConfiguration().isPresent(); 789 } 790 791 private boolean _hasOverrides(ContentType contentType, String viewName) 792 { 793 Optional<ViewConfigurations> optionalViewConfigurations = contentType.getViewConfigurations(viewName); 794 return optionalViewConfigurations.isPresent() && !optionalViewConfigurations.get().overrides().isEmpty(); 795 } 796 797 /** 798 * Get the views of a content type 799 * @param contentTypeId the content type id 800 * @param includeInternals Set to true to include internal views. 801 * @return the views' info 802 */ 803 public List<Map<String, Object>> getViewsInfo(String contentTypeId, boolean includeInternals) 804 { 805 List<Map<String, Object>> views = new ArrayList<>(); 806 807 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 808 809 Set<String> viewNames = contentType.getViewNames(includeInternals); 810 for (String viewName : viewNames) 811 { 812 View view = contentType.getView(viewName); 813 814 Map<String, Object> viewInfos = new HashMap<>(); 815 viewInfos.put("name", viewName); 816 viewInfos.put("label", view.getLabel()); 817 viewInfos.put("description", view.getDescription()); 818 views.add(viewInfos); 819 } 820 821 return views; 822 } 823 824 /** 825 * Retrieves the model item at the given path 826 * @param path path of the model item to retrieve. No matter if it is a definition or data path (with repeater entry positions) 827 * @param cTypes identifiers of the content types where to search the model item 828 * @param mixins identifiers of the mixins where to search the model item 829 * @return the model item 830 * @throws IllegalArgumentException if the given path is null or empty 831 * @throws UndefinedItemPathException if there is no item defined at the given path in given item containers 832 */ 833 public ModelItem getModelItem(String path, String[] cTypes, String[] mixins) throws IllegalArgumentException, UndefinedItemPathException 834 { 835 Collection<ContentType> contentTypes = new ArrayList<>(); 836 837 String[] allContentTypes = ArrayUtils.addAll(cTypes, mixins); 838 for (String contentTypeId : allContentTypes) 839 { 840 if (_getContentTypeEP().hasExtension(contentTypeId)) 841 { 842 contentTypes.add(_getContentTypeEP().getExtension(contentTypeId)); 843 } 844 else 845 { 846 if (getLogger().isWarnEnabled()) 847 { 848 getLogger().warn("Unknown content type identifier : " + contentTypeId); 849 } 850 } 851 } 852 853 return ModelHelper.getModelItem(path, contentTypes); 854 } 855 856 /** 857 * Retrieve the list of successive model items represented by the given paths, indexed by path. 858 * @param paths paths of the model items to retrieve. These paths are relative to the given containers. No matter if they are definition or data paths (with repeater entry positions) 859 * @param content the content 860 * @return the list of successive model items, indexed by path 861 * @throws IllegalArgumentException if one of the given paths is null or empty 862 * @throws UndefinedItemPathException if there is no item defined at one of the given paths in given item containers 863 */ 864 public Map<String, List<ModelItem>> getModelItemsPaths(Set<String> paths, Content content) 865 { 866 return ModelHelper.getAllModelItemsInPaths(paths, content.getModel()); 867 } 868 869 /** 870 * Retrieve the list of successive model items represented by the given path. 871 * @param path path of the model items to retrieve. This path is relative to the given containers. No matter if it is a definition or data path (with repeater entry positions) 872 * @param content the content 873 * @return the list of successive model items 874 * @throws IllegalArgumentException if the given path is null or empty 875 * @throws UndefinedItemPathException if there is no item defined at the given path in given item containers 876 */ 877 public List<ModelItem> getModelItemPath(String path, Content content) throws IllegalArgumentException, UndefinedItemPathException 878 { 879 return ModelHelper.getAllModelItemsInPath(path, content.getModel()); 880 } 881 882 /** 883 * Retrieve the list of successive model items represented by the given paths, indexed by path. 884 * @param paths paths of the model items to retrieve. These paths are relative to the given containers. No matter if they are definition or data paths (with repeater entry positions) 885 * @param contentType the content type 886 * @return the list of successive model items, indexed by path 887 * @throws IllegalArgumentException if one of the given paths is null or empty 888 * @throws UndefinedItemPathException if there is no item defined at one of the given paths in given item containers 889 */ 890 public Map<String, List<ModelItem>> getModelItemsPaths(Set<String> paths, ContentType contentType) 891 { 892 return ModelHelper.getAllModelItemsInPaths(paths, Collections.singleton(contentType)); 893 } 894 895 /** 896 * Retrieve the list of successive model items represented by the given path. 897 * @param path path of the model items to retrieve. This path is relative to the given containers. No matter if it is a definition or data path (with repeater entry positions) 898 * @param contentType the content type 899 * @return the list of successive model items 900 * @throws IllegalArgumentException if the given path is null or empty 901 * @throws UndefinedItemPathException if there is no item defined at the given path in given item containers 902 */ 903 public List<ModelItem> getModelItemPath(String path, ContentType contentType) 904 { 905 return ModelHelper.getAllModelItemsInPath(path, Collections.singleton(contentType)); 906 } 907 908 /** 909 * Determines if the given content type can be added to content 910 * 911 * @param content The content 912 * @param cTypeId The id of content type 913 * @return <code>true</code> if the content type is compatible with content 914 */ 915 public boolean isCompatibleContentType(Content content, String cTypeId) 916 { 917 String[] currentContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 918 919 ArrayList<String> cTypes = new ArrayList<>(Arrays.asList(currentContentTypes)); 920 cTypes.add(cTypeId); 921 922 try 923 { 924 getModelItems(cTypes.toArray(new String[cTypes.size()])); 925 return true; 926 } 927 catch (IllegalArgumentException e) 928 { 929 return false; 930 } 931 } 932 933 /** 934 * Retrieves all model items of given content types, indexed by their names. 935 * @param contentTypeIds The identifier of the content types 936 * @return the model items 937 * @throws IllegalArgumentException if some content types define a model item with the same name and this model item does not come from a common ancestor 938 */ 939 public Map<String, ModelItem> getModelItemsIndexedByName(String[] contentTypeIds) throws IllegalArgumentException 940 { 941 return getModelItems(contentTypeIds).stream() 942 .collect(Collectors.toMap(ModelItem::getName, Function.identity())); 943 } 944 945 /** 946 * Retrieves all model items of given content types. 947 * @param contentTypeIds The identifier of the content types 948 * @return the model items 949 * @throws IllegalArgumentException if some content types define a model item with the same name and this model item does not come from a common ancestor 950 */ 951 public Collection<? extends ModelItem> getModelItems(String[] contentTypeIds) throws IllegalArgumentException 952 { 953 List<ContentType> contentTypes = Arrays.stream(contentTypeIds) 954 .map(id -> _getContentTypeEP().getExtension(id)) 955 .collect(Collectors.toList()); 956 957 return ModelHelper.getModelItems(contentTypes); 958 } 959 960 /** 961 * Retrieves the common model items for a list of content types 962 * @param contentTypeIds The list of content types to consider 963 * @param viewName The view name to list model items 964 * @return The map of model items. Key are the model item's path in the content type 965 */ 966 public Map<String, ModelItem> getCommonModelItems(Collection<String> contentTypeIds, String viewName) 967 { 968 Map<String, ModelItem> commonModelItems = null; 969 970 for (String contentTypeId : contentTypeIds) 971 { 972 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 973 View view = contentType.getView(viewName); 974 975 Map<String, ModelItem> accumulator = new LinkedHashMap<>(); 976 if (view != null) 977 { 978 _populateModelItemsAccumulator(accumulator, view, StringUtils.EMPTY); 979 } 980 981 if (commonModelItems == null) 982 { 983 commonModelItems = new LinkedHashMap<>(); 984 for (Map.Entry<String, ModelItem> accumulatorEntry : accumulator.entrySet()) 985 { 986 commonModelItems.put(accumulatorEntry.getKey(), accumulatorEntry.getValue()); 987 } 988 } 989 else 990 { 991 // only retains common attributes (performs a set intersection) 992 commonModelItems.keySet().retainAll(accumulator.keySet()); 993 } 994 } 995 996 return commonModelItems != null ? commonModelItems : Collections.emptyMap(); 997 } 998 999 /** 1000 * Populates the accumulator with the model items 1001 * @param internalAcc the accumulator of model items 1002 * @param viewItemContainer the view item container of the content 1003 * @param prefix the view item accessor path prefix 1004 */ 1005 protected void _populateModelItemsAccumulator(Map<String, ModelItem> internalAcc, ViewItemContainer viewItemContainer, String prefix) 1006 { 1007 for (ViewItem viewItem : viewItemContainer.getViewItems()) 1008 { 1009 String newPrefix = prefix; 1010 1011 if (viewItem instanceof ModelViewItem) 1012 { 1013 ModelItem modelItem = ((ModelViewItem) viewItem).getDefinition(); 1014 if (modelItem != null) 1015 { 1016 internalAcc.put(modelItem.getPath(), modelItem); 1017 1018 if (viewItem instanceof ViewItemContainer) 1019 { 1020 newPrefix += ModelItem.ITEM_PATH_SEPARATOR + modelItem.getName(); 1021 } 1022 } 1023 } 1024 1025 if (viewItem instanceof ViewItemContainer) 1026 { 1027 _populateModelItemsAccumulator(internalAcc, (ViewItemContainer) viewItem, newPrefix); 1028 } 1029 } 1030 } 1031 1032 /** 1033 * Get information on content types.<br> 1034 * @return A Map with content types 1035 */ 1036 public Set<Map<String, Object>> getContentTypesInformations() 1037 { 1038 // Collect content types. 1039 List<String> allContentTypesIds = _getContentTypeEP() 1040 .getExtensionsIds() 1041 .stream() 1042 .collect(Collectors.toList()); 1043 return getContentTypesInformations(allContentTypesIds, true); 1044 } 1045 1046 /** 1047 * Get the content types information as JSON 1048 * @param contentTypeIds the list of content type ids 1049 * @param withRight true to add rights to results 1050 * @return the list of content types information as JSON 1051 */ 1052 @Callable 1053 public Set<Map<String, Object>> getContentTypesInformations(List<String> contentTypeIds, boolean withRight) 1054 { 1055 Set<Map<String, Object>> results = new HashSet<>(); 1056 for (String contentTypeId : contentTypeIds) 1057 { 1058 ContentType cType = _cTypeEP.getExtension(contentTypeId); 1059 if (cType != null) 1060 { 1061 Map<String, Object> contentTypeProperties = getContentTypeProperties(cType); 1062 if (withRight) 1063 { 1064 contentTypeProperties.put("rightEvaluated", _hasRight(cType)); 1065 } 1066 results.add(contentTypeProperties); 1067 } 1068 } 1069 1070 return results; 1071 } 1072 1073 /** 1074 * Get information on content types.<br> 1075 * 1076 * @param ids The id of content types to retrieve 1077 * @param inherited If true, the sub-types will be also returned. 1078 * @param checkRights If true, only content types allowed for creation will 1079 * be returned 1080 * @param includePrivate If true, the list will include the private content types. By default the list is restricted to the public content types. 1081 * @param includeMixins If true the list will include the mixins content types. 1082 * @param includeAbstract If true the list will include the abstract content types. 1083 * @return A Map with retrieved, unknown, not-allowed and private content types 1084 */ 1085 @Callable 1086 public Map<String, Object> getContentTypesList (List<String> ids, boolean inherited, boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract) 1087 { 1088 // Collect content types. 1089 Collection<String> allContentTypesIds = new ArrayList<>(); 1090 if (ids == null || ids.isEmpty()) 1091 { 1092 allContentTypesIds = _getContentTypeEP().getExtensionsIds(); 1093 } 1094 else 1095 { 1096 for (String id : ids) 1097 { 1098 _addIfNotPresent(allContentTypesIds, id); 1099 1100 if (inherited) 1101 { 1102 for (String subTypeId : _getContentTypeEP().getSubTypes(id)) 1103 { 1104 _addIfNotPresent(allContentTypesIds, subTypeId); 1105 } 1106 } 1107 } 1108 } 1109 1110 // Resolve and organize content types 1111 return _dispatchContentTypes(checkRights, includePrivate, includeMixins, includeAbstract, allContentTypesIds); 1112 } 1113 1114 private Map<String, Object> _dispatchContentTypes(boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract, Collection<String> allContentTypesIds) 1115 { 1116 List<Map<String, Object>> contentTypes = new ArrayList<>(); 1117 List<String> unknownContentTypes = new ArrayList<>(); 1118 List<String> noRightContentTypes = new ArrayList<>(); 1119 List<String> privateContentTypes = new ArrayList<>(); 1120 List<String> mixinContentTypes = new ArrayList<>(); 1121 List<String> abstractContentTypes = new ArrayList<>(); 1122 1123 for (String id : allContentTypesIds) 1124 { 1125 ContentType cType = _getContentTypeEP().getExtension(id); 1126 1127 if (cType != null) 1128 { 1129 if (cType.isAbstract() && !includeAbstract) 1130 { 1131 abstractContentTypes.add(id); 1132 } 1133 else if (cType.isPrivate() && !includePrivate) 1134 { 1135 privateContentTypes.add(id); 1136 } 1137 else if (cType.isMixin() && !includeMixins) 1138 { 1139 mixinContentTypes.add(id); 1140 } 1141 else if (!checkRights || _hasRight(cType)) 1142 { 1143 contentTypes.add(getContentTypeProperties(cType)); 1144 } 1145 else 1146 { 1147 noRightContentTypes.add(id); 1148 } 1149 } 1150 else 1151 { 1152 unknownContentTypes.add(id); 1153 } 1154 } 1155 1156 Map<String, Object> result = new HashMap<>(); 1157 1158 result.put("contentTypes", contentTypes); 1159 result.put("noRightContentTypes", noRightContentTypes); 1160 result.put("unknownContentTypes", unknownContentTypes); 1161 result.put("privateContentTypes", privateContentTypes); 1162 result.put("mixinContentTypes", mixinContentTypes); 1163 result.put("abstractContentTypes", abstractContentTypes); 1164 1165 return result; 1166 } 1167 1168 /** 1169 * Get the content type properties 1170 * 1171 * @param contentType The content type 1172 * @return The content type properties 1173 */ 1174 public Map<String, Object> getContentTypeProperties(ContentType contentType) 1175 { 1176 Map<String, Object> infos = new HashMap<>(); 1177 1178 infos.put("id", contentType.getId()); 1179 infos.put("label", contentType.getLabel()); 1180 infos.put("description", contentType.getDescription()); 1181 infos.put("defaultTitle", contentType.getDefaultTitle()); 1182 infos.put("iconGlyph", contentType.getIconGlyph()); 1183 infos.put("iconDecorator", contentType.getIconDecorator()); 1184 infos.put("iconSmall", contentType.getSmallIcon()); 1185 infos.put("iconMedium", contentType.getMediumIcon()); 1186 infos.put("iconLarge", contentType.getLargeIcon()); 1187 infos.put("right", contentType.getRight()); 1188 infos.put("isMultilingual", contentType.isMultilingual()); 1189 infos.put("isSimple", contentType.isSimple()); 1190 infos.put("isPrivate", contentType.isPrivate()); 1191 infos.put("isAbstract", contentType.isAbstract()); 1192 infos.put("isReferenceTable", contentType.isReferenceTable()); 1193 infos.put("isMixin", contentType.isMixin()); 1194 infos.put("superTypes", contentType.getSupertypeIds()); 1195 infos.put("tags", contentType.getTags()); 1196 infos.put("parentModelItemName", contentType.getParentAttributeDefinition() 1197 .map(ContentAttributeDefinition::getPath) 1198 .orElse(StringUtils.EMPTY)); 1199 infos.put("viewNames", contentType.getViewNames(true)); 1200 1201 return infos; 1202 } 1203 1204 /** 1205 * Test if the current user has the right needed by the content type to create a content. 1206 * @param contentTypeId The content type id 1207 * @return true if the user has the right needed, false otherwise. 1208 */ 1209 public boolean hasRight(String contentTypeId) 1210 { 1211 return Optional.ofNullable(contentTypeId) 1212 .filter(_getContentTypeEP()::hasExtension) 1213 .map(_getContentTypeEP()::getExtension) 1214 .map(this::_hasRight) 1215 .orElse(false); 1216 } 1217 1218 /** 1219 * Test if the current user has the right needed by the content type to create a content. 1220 * @param contentType The content type 1221 * @return true if the user has the right needed, false otherwise. 1222 */ 1223 protected boolean _hasRight(ContentType contentType) 1224 { 1225 boolean hasRight = false; 1226 1227 String right = contentType.getRight(); 1228 1229 if (right == null) 1230 { 1231 hasRight = true; 1232 } 1233 else 1234 { 1235 UserIdentity user = _userProvider.getUser(); 1236 hasRight = _rightManager.hasRight(user, right, "/cms") == RightResult.RIGHT_ALLOW || _rightManager.hasRight(user, right, _rootContentHelper.getRootContent()) == RightResult.RIGHT_ALLOW; 1237 } 1238 1239 return hasRight; 1240 } 1241 1242 private void _addIfNotPresent(Collection<String> collection, String value) 1243 { 1244 if (!collection.contains(value)) 1245 { 1246 collection.add(value); 1247 } 1248 } 1249 1250 /** 1251 * Get the id of content type to use for rendering 1252 * 1253 * @param content The content 1254 * @return the id of dynamic or standard content type 1255 */ 1256 public String getContentTypeIdForRendering(Content content) 1257 { 1258 String dynamicContentTypeId = getDynamicContentTypeId(content.getTypes(), content.getMixinTypes()); 1259 if (dynamicContentTypeId != null) 1260 { 1261 return dynamicContentTypeId; 1262 } 1263 1264 ContentType firstContentType = getFirstContentType(content); 1265 return firstContentType != null ? firstContentType.getId() : StringUtils.EMPTY; 1266 } 1267 1268 /** 1269 * Get the id of the dynamic content type matching to given ones 1270 * @param contentTypes The content types 1271 * @param mixinTypes The mixins 1272 * @return the id of dynamic content type or null if no dynamic content type was found 1273 */ 1274 public String getDynamicContentTypeId(String[] contentTypes, String[] mixinTypes) 1275 { 1276 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(contentTypes, mixinTypes); 1277 if (dynamicContentType != null) 1278 { 1279 return dynamicContentType.getId(); 1280 } 1281 1282 return null; 1283 } 1284 1285 /** 1286 * Get the plugin name of content type to use for rendering 1287 * 1288 * @param content The content 1289 * @return the plugin name of dynamic or standard content type 1290 */ 1291 public String getContentTypePluginForRendering(Content content) 1292 { 1293 String dynamicContentTypePlugin = getDynamicContentTypePlugin(content.getTypes(), content.getMixinTypes()); 1294 if (dynamicContentTypePlugin != null) 1295 { 1296 return dynamicContentTypePlugin; 1297 } 1298 1299 ContentType firstContentType = getFirstContentType(content); 1300 return firstContentType != null ? firstContentType.getPluginName() : StringUtils.EMPTY; 1301 } 1302 1303 /** 1304 * Get the plugin name of the dynamic content type matching to given ones 1305 * @param contentTypes The content types 1306 * @param mixinTypes The mixins 1307 * @return the plugin name of dynamic content type or null if no dynamic content type was found 1308 */ 1309 public String getDynamicContentTypePlugin(String[] contentTypes, String[] mixinTypes) 1310 { 1311 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(contentTypes, mixinTypes); 1312 if (dynamicContentType != null) 1313 { 1314 return dynamicContentType.getPluginName(); 1315 } 1316 1317 return null; 1318 } 1319 1320 /** 1321 * Retrieves the label of the content type. 1322 * 1323 * @param content The content 1324 * @return the label. 1325 */ 1326 public I18nizableText getContentTypeLabel(Content content) 1327 { 1328 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1329 if (dynamicContentType != null) 1330 { 1331 return dynamicContentType.getLabel(); 1332 } 1333 1334 ContentType firstContentType = getFirstContentType(content); 1335 return firstContentType != null ? firstContentType.getLabel() : new I18nizableText(StringUtils.EMPTY); 1336 } 1337 1338 /** 1339 * Retrieves the description of the content type. 1340 * 1341 * @param content The content 1342 * @return the label. 1343 */ 1344 public I18nizableText getContentTypeDescription(Content content) 1345 { 1346 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1347 if (dynamicContentType != null) 1348 { 1349 return dynamicContentType.getDescription(); 1350 } 1351 1352 ContentType firstContentType = getFirstContentType(content); 1353 return firstContentType != null ? firstContentType.getDescription() : new I18nizableText(StringUtils.EMPTY); 1354 } 1355 1356 /** 1357 * Retrieves the default title of the content type. 1358 * 1359 * @param content The content 1360 * @return the label. 1361 */ 1362 public I18nizableText getContentTypeDefaultTitle(Content content) 1363 { 1364 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1365 if (dynamicContentType != null) 1366 { 1367 return dynamicContentType.getDefaultTitle(); 1368 } 1369 1370 ContentType firstContentType = getFirstContentType(content); 1371 return firstContentType != null ? firstContentType.getDefaultTitle() : new I18nizableText(StringUtils.EMPTY); 1372 } 1373 1374 /** 1375 * Retrieves the category of the content type. 1376 * 1377 * @param content The content 1378 * @return the label. 1379 */ 1380 public I18nizableText getContentTypeCategory(Content content) 1381 { 1382 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1383 if (dynamicCTDescriptor != null) 1384 { 1385 return dynamicCTDescriptor.getCategory(); 1386 } 1387 1388 ContentType firstContentType = getFirstContentType(content); 1389 return firstContentType != null ? firstContentType.getCategory() : new I18nizableText(StringUtils.EMPTY); 1390 } 1391 1392 /** 1393 * Retrieves the CSS class to use as glyph icon of the content 1394 * @param content The content 1395 * @return the glyph 1396 */ 1397 public String getIconGlyph(Content content) 1398 { 1399 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1400 if (dynamicCTDescriptor != null) 1401 { 1402 return dynamicCTDescriptor.getIconGlyph(); 1403 } 1404 1405 ContentType firstContentType = getFirstContentType(content); 1406 return firstContentType != null ? firstContentType.getIconGlyph() : null; 1407 } 1408 1409 /** 1410 * Retrieves the CSS class to use as decorator above the main icon 1411 * @param content The content 1412 * @return the decorator CSS class name 1413 */ 1414 public String getIconDecorator(Content content) 1415 { 1416 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1417 if (dynamicCTDescriptor != null) 1418 { 1419 return dynamicCTDescriptor.getIconDecorator(); 1420 } 1421 1422 ContentType firstContentType = getFirstContentType(content); 1423 return firstContentType != null ? firstContentType.getIconDecorator() : null; 1424 } 1425 1426 /** 1427 * Retrieves the URL of the icon without the context path. 1428 * 1429 * @param content The content 1430 * @return the icon URL for the small image 16x16. 1431 */ 1432 public String getSmallIcon(Content content) 1433 { 1434 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1435 if (dynamicCTDescriptor != null) 1436 { 1437 return dynamicCTDescriptor.getSmallIcon(); 1438 } 1439 1440 ContentType firstContentType = getFirstContentType(content); 1441 return firstContentType != null ? firstContentType.getSmallIcon() : StringUtils.EMPTY; 1442 } 1443 1444 /** 1445 * Retrieves the URL of the icon without the context path. 1446 * 1447 * @param content The content 1448 * @return the icon URL for the medium image 32x32. 1449 */ 1450 public String getMediumIcon(Content content) 1451 { 1452 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1453 if (dynamicCTDescriptor != null) 1454 { 1455 return dynamicCTDescriptor.getMediumIcon(); 1456 } 1457 1458 ContentType firstContentType = getFirstContentType(content); 1459 return firstContentType != null ? firstContentType.getMediumIcon() : StringUtils.EMPTY; 1460 } 1461 1462 /** 1463 * Retrieves the URL of the icon without the context path. 1464 * 1465 * @param content The content 1466 * @return the icon URL for the large image 48x48. 1467 */ 1468 public String getLargeIcon(Content content) 1469 { 1470 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1471 if (dynamicCTDescriptor != null) 1472 { 1473 return dynamicCTDescriptor.getLargeIcon(); 1474 } 1475 1476 ContentType firstContentType = getFirstContentType(content); 1477 return firstContentType != null ? firstContentType.getLargeIcon() : StringUtils.EMPTY; 1478 } 1479 1480 /** 1481 * Get the content type which determines the content icons and rendering 1482 * 1483 * @param content The content 1484 * @return The main content type 1485 */ 1486 public ContentType getFirstContentType(Content content) 1487 { 1488 TreeSet<ContentType> treeSet = new TreeSet<>(new ContentTypeComparator()); 1489 1490 for (String id : content.getTypes()) 1491 { 1492 ContentType contentType = _getContentTypeEP().getExtension(id); 1493 if (contentType != null) 1494 { 1495 treeSet.add(contentType); 1496 } 1497 else 1498 { 1499 if (getLogger().isWarnEnabled()) 1500 { 1501 getLogger().warn(String.format("Trying to get an unknown content type : '%s'.", id)); 1502 } 1503 } 1504 } 1505 1506 return !treeSet.isEmpty() ? treeSet.first() : null; 1507 } 1508 1509 /** 1510 * Retrieves the definition for the standard "title" attribute. 1511 * @return the standard "title" attribute. 1512 */ 1513 public AttributeDefinition<String> getTitleAttributeDefinition() 1514 { 1515 AttributeDefinition<String> definition = new AttributeDefinition<>(); 1516 definition.setName(Content.ATTRIBUTE_TITLE); 1517 definition.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_LABEL")); 1518 definition.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_DESCRIPTION")); 1519 definition.setType(_contentAttributeTypeExtensionPoint.getExtension(ModelItemTypeConstants.STRING_TYPE_ID)); 1520 definition.setMultiple(false); 1521 return definition; 1522 } 1523 1524 /** 1525 * Determines if the content type is an archived content type 1526 * @param cTypeId The id of content type 1527 * @return true if the content type is an org.ametys.cms.ArchivedContent 1528 */ 1529 public boolean isArchivedContentType(String cTypeId) 1530 { 1531 return getAncestors(cTypeId).contains(ARCHIVED_CONTENT_TYPE); 1532 } 1533 1534 class ContentTypeComparator implements Comparator<ContentType> 1535 { 1536 @Override 1537 public int compare(ContentType c1, ContentType c2) 1538 { 1539 I18nizableText t1 = c1.getLabel(); 1540 I18nizableText t2 = c2.getLabel(); 1541 1542 String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); 1543 String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); 1544 1545 int compareTo = str1.toString().compareTo(str2.toString()); 1546 if (compareTo == 0) 1547 { 1548 // Content types have same keys but there are not equals, so do 1549 // not return 0 to add it in TreeSet 1550 // Indeed, in a TreeSet implementation two elements that are 1551 // equal by the method compareTo are, from the standpoint of the 1552 // set, equal 1553 return 1; 1554 } 1555 return compareTo; 1556 } 1557 } 1558 1559 private Cache<CacheKey, View> _getViewCache() 1560 { 1561 return _cacheManager.get(__VIEW_CACHE); 1562 } 1563 1564 static final class CacheKey extends AbstractCacheKey 1565 { 1566 private CacheKey(Set<String> contentTypeIds, Set<String> mixinIds, String viewName) 1567 { 1568 super(contentTypeIds, mixinIds, viewName); 1569 } 1570 1571 static CacheKey of(Set<String> contentTypeIds, Set<String> mixinIds, String viewName) 1572 { 1573 return new CacheKey(contentTypeIds, mixinIds, viewName); 1574 } 1575 } 1576}