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.stream.Collectors; 034 035import org.apache.avalon.framework.activity.Disposable; 036import org.apache.avalon.framework.activity.Initializable; 037import org.apache.avalon.framework.component.Component; 038import org.apache.avalon.framework.configuration.Configuration; 039import org.apache.avalon.framework.configuration.ConfigurationException; 040import org.apache.avalon.framework.context.Context; 041import org.apache.avalon.framework.context.ContextException; 042import org.apache.avalon.framework.context.Contextualizable; 043import org.apache.avalon.framework.logger.AbstractLogEnabled; 044import org.apache.avalon.framework.service.ServiceException; 045import org.apache.avalon.framework.service.ServiceManager; 046import org.apache.avalon.framework.service.Serviceable; 047import org.apache.avalon.framework.thread.ThreadSafe; 048import org.apache.cocoon.ProcessingException; 049import org.apache.cocoon.components.LifecycleHelper; 050import org.apache.commons.collections.CollectionUtils; 051import org.apache.commons.lang3.ArrayUtils; 052import org.apache.commons.lang3.StringUtils; 053import org.apache.commons.lang3.tuple.Pair; 054 055import org.ametys.cms.content.RootContentHelper; 056import org.ametys.cms.contenttype.indexing.IndexingField; 057import org.ametys.cms.contenttype.indexing.IndexingModel; 058import org.ametys.cms.repository.Content; 059import org.ametys.cms.repository.ContentAttributeTypeExtensionPoint; 060import org.ametys.core.cache.AbstractCacheManager; 061import org.ametys.core.cache.Cache; 062import org.ametys.core.right.RightManager; 063import org.ametys.core.right.RightManager.RightResult; 064import org.ametys.core.ui.Callable; 065import org.ametys.core.user.CurrentUserProvider; 066import org.ametys.core.user.UserIdentity; 067import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 068import org.ametys.plugins.repository.AmetysRepositoryException; 069import org.ametys.runtime.i18n.I18nizableText; 070import org.ametys.runtime.model.DefinitionContext; 071import org.ametys.runtime.model.ElementDefinition; 072import org.ametys.runtime.model.ModelHelper; 073import org.ametys.runtime.model.ModelItem; 074import org.ametys.runtime.model.ModelViewItem; 075import org.ametys.runtime.model.View; 076import org.ametys.runtime.model.ViewElement; 077import org.ametys.runtime.model.ViewItem; 078import org.ametys.runtime.model.ViewItemContainer; 079import org.ametys.runtime.model.ViewParser; 080import org.ametys.runtime.model.exception.UndefinedItemPathException; 081import org.ametys.runtime.model.type.ModelItemTypeConstants; 082 083/** 084 * Helper for manipulating {@link ContentType}s 085 */ 086public class ContentTypesHelper extends AbstractLogEnabled implements Component, Serviceable, ThreadSafe, Disposable, Initializable, Contextualizable 087{ 088 /** The Avalon role */ 089 public static final String ROLE = ContentTypesHelper.class.getName(); 090 091 /** Archived content type */ 092 public static final String ARCHIVED_CONTENT_TYPE = "org.ametys.cms.ArchivedContent"; 093 094 private static final String __VIEW_METADATASET = ContentTypesHelper.class.getName() + "$view.metadataset"; 095 096 private static final String __EDITION_METADATASET = ContentTypesHelper.class.getName() + "$edition.metadataset"; 097 098 private static final String __VIEW_CACHE = ContentTypesHelper.class.getName() + "$view.cache"; 099 100 /** The content types extension point */ 101 protected ContentTypeExtensionPoint _cTypeEP; 102 /** The current user provider */ 103 protected CurrentUserProvider _userProvider; 104 /** The rights manager */ 105 protected RightManager _rightManager; 106 /** Helper for root content */ 107 protected RootContentHelper _rootContentHelper; 108 /** The extension point with the available types for contents */ 109 protected ContentAttributeTypeExtensionPoint _contentAttributeTypeExtensionPoint; 110 111 private DynamicContentTypeDescriptorExtentionPoint _dynamicCTDescriptorEP; 112 113 private Context _context; 114 private ServiceManager _smanager; 115 116 private AbstractCacheManager _cacheManager; 117 118 public void contextualize(Context context) throws ContextException 119 { 120 _context = context; 121 } 122 123 @Override 124 public void service(ServiceManager smanager) throws ServiceException 125 { 126 _smanager = smanager; 127 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 128 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 129 _rootContentHelper = (RootContentHelper) smanager.lookup(RootContentHelper.ROLE); 130 _contentAttributeTypeExtensionPoint = (ContentAttributeTypeExtensionPoint) smanager.lookup(ContentAttributeTypeExtensionPoint.ROLE); 131 _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE); 132 } 133 134 /** 135 * Initialize 136 */ 137 @Override 138 public void initialize() 139 { 140 _createCaches(); 141 } 142 143 /** 144 * Creates the caches 145 */ 146 protected void _createCaches() 147 { 148 _cacheManager.createMemoryCache(__VIEW_METADATASET, 149 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_METADATASET_LABEL"), 150 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_METADATASET_DESCRIPTION"), 151 true, 152 null); 153 _cacheManager.createMemoryCache(__EDITION_METADATASET, 154 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_EDITION_METADATASET_LABEL"), 155 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_EDITION_METADATASET_DESCRIPTION"), 156 true, 157 null); 158 _cacheManager.createMemoryCache(__VIEW_CACHE, 159 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_LABEL"), 160 new I18nizableText("plugin.cms", "PLUGINS_CMS_CACHE_VIEW_DESCRIPTION"), 161 true, 162 null); 163 } 164 165 private DynamicContentTypeDescriptorExtentionPoint _getDynamicContentTypeDescriptorExtentionPoint() 166 { 167 if (_dynamicCTDescriptorEP == null) 168 { 169 try 170 { 171 _dynamicCTDescriptorEP = (DynamicContentTypeDescriptorExtentionPoint) _smanager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE); 172 } 173 catch (ServiceException e) 174 { 175 throw new IllegalStateException(e); 176 } 177 } 178 return _dynamicCTDescriptorEP; 179 } 180 181 @Override 182 public void dispose() 183 { 184 _getCacheForView().invalidateAll(); 185 _getCacheForEdition().invalidateAll(); 186 _getViewCache().invalidateAll(); 187 } 188 189 /** 190 * Lazy lookup of {@link ContentTypeExtensionPoint} 191 * @return the content type extension point 192 */ 193 protected ContentTypeExtensionPoint _getContentTypeEP() 194 { 195 if (_cTypeEP == null) 196 { 197 try 198 { 199 _cTypeEP = (ContentTypeExtensionPoint) _smanager.lookup(ContentTypeExtensionPoint.ROLE); 200 } 201 catch (ServiceException e) 202 { 203 throw new RuntimeException("Unable to lookup ContentTypeExtensionPoint component", e); 204 } 205 } 206 return _cTypeEP; 207 } 208 209 /** 210 * Determines if a content is a instance of given content type id 211 * 212 * @param content The content 213 * @param cTypeId The id of content type or mixin 214 * @return <code>true</code> if the content is an instance of content type 215 */ 216 public boolean isInstanceOf(Content content, String cTypeId) 217 { 218 String[] types = content.getTypes(); 219 if (ArrayUtils.contains(types, cTypeId)) 220 { 221 return true; 222 } 223 224 String[] mixins = content.getMixinTypes(); 225 if (ArrayUtils.contains(mixins, cTypeId)) 226 { 227 return true; 228 } 229 230 return _containsContentType(ArrayUtils.addAll(types, mixins), cTypeId); 231 } 232 233 private boolean _containsContentType(String[] cTypesId, String cTypeId) 234 { 235 for (String id : cTypesId) 236 { 237 ContentType cType = _getContentTypeEP().getExtension(id); 238 if (cType != null) 239 { 240 if (ArrayUtils.contains(cType.getSupertypeIds(), cTypeId)) 241 { 242 return true; 243 } 244 else if (_containsContentType(cType.getSupertypeIds(), cTypeId)) 245 { 246 return true; 247 } 248 } 249 } 250 return false; 251 } 252 253 /** 254 * Get the identifiers of the content types common ancestors 255 * @param contentTypeIds The identifiers of the content types to compare 256 * @return The identifiers of common ancestors 257 */ 258 public Set<String> getCommonAncestors(Collection<String> contentTypeIds) 259 { 260 Set<String> commonAncestors = new HashSet<>(); 261 262 // Get ancestors of each content type 263 List<Collection<String>> superTypeIdsByCType = new ArrayList<>(); 264 for (String contentTypeId : contentTypeIds) 265 { 266 Set<String> superTypeIds = new HashSet<>(); 267 268 superTypeIds.add(contentTypeId); 269 superTypeIds.addAll(getAncestors(contentTypeId)); 270 271 superTypeIdsByCType.add(superTypeIds); 272 } 273 274 // Make the intersection of all the ancestors collections 275 if (!superTypeIdsByCType.isEmpty()) 276 { 277 Iterator<Collection<String>> superTypeIdsByCTypeIt = superTypeIdsByCType.iterator(); 278 commonAncestors.addAll(superTypeIdsByCTypeIt.next()); 279 while (superTypeIdsByCTypeIt.hasNext() && !commonAncestors.isEmpty()) 280 { 281 commonAncestors.retainAll(superTypeIdsByCTypeIt.next()); 282 } 283 } 284 285 // Remove ancestors of ancestors: their attributes will be given by the first one 286 commonAncestors = removeAncestors(commonAncestors); 287 288 return commonAncestors; 289 } 290 291 /** 292 * Remove all content types in the set that are ancestors of other content types in the set. 293 * @param contentTypes a Set of content type IDs. 294 * @return a Set of content type IDs without ancestors. 295 */ 296 protected Set<String> removeAncestors(Set<String> contentTypes) 297 { 298 Set<String> noAncestors = new HashSet<>(contentTypes); 299 300 Iterator<String> it1 = contentTypes.iterator(); 301 while (it1.hasNext()) 302 { 303 ContentType cType1 = _getContentTypeEP().getExtension(it1.next()); 304 305 Iterator<String> it2 = contentTypes.iterator(); 306 while (it2.hasNext()) 307 { 308 String cType2 = it2.next(); 309 String[] supertypeIds = cType1.getSupertypeIds(); 310 311 // CType2 is an ancestor of CType1: remove it. 312 if (ArrayUtils.contains(supertypeIds, cType2)) 313 { 314 noAncestors.remove(cType2); 315 } 316 } 317 } 318 319 return noAncestors; 320 } 321 322 /** 323 * Get all ancestors for the given content type 324 * 325 * @param contentTypeId The content type id to test 326 * @return A non-null set of all ancestors. Does not contains the contentTypeId itself. 327 * @throws IllegalArgumentException if the content type does not exist. 328 */ 329 public Set<String> getAncestors(String contentTypeId) 330 { 331 Set<String> superTypes = new HashSet<>(); 332 333 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 334 335 if (cType == null) 336 { 337 throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'"); 338 } 339 340 341 String[] supertypeIds = cType.getSupertypeIds(); 342 343 for (String superTypeId : supertypeIds) 344 { 345 superTypes.add(superTypeId); 346 superTypes.addAll(getAncestors(superTypeId)); 347 } 348 349 return superTypes; 350 351 } 352 353 /** 354 * Get super type's ids for the given content type 355 * The first entry contains super content types, the second one contains super mixin types 356 * 357 * @param contentTypeId The content type id to test 358 * @return An array of super type's ids. 359 * @throws IllegalArgumentException if the content type does not exist. 360 */ 361 public Pair<String[], String[]> getSupertypeIds(String contentTypeId) 362 { 363 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 364 if (contentType == null) 365 { 366 throw new IllegalArgumentException("Unable to get super types of unknown content type '" + contentTypeId + "'"); 367 } 368 369 List<String> superMixins = new ArrayList<>(); 370 List<String> superContentTypes = new ArrayList<>(); 371 for (String supertypeId : contentType.getSupertypeIds()) 372 { 373 ContentType supertype = _getContentTypeEP().getExtension(supertypeId); 374 if (supertype == null) 375 { 376 throw new IllegalArgumentException("Unable to get the unknown super type '" + supertypeId + "' for type '" + contentTypeId + "'"); 377 } 378 379 if (supertype.isMixin()) 380 { 381 superMixins.add(supertypeId); 382 } 383 else 384 { 385 superContentTypes.add(supertypeId); 386 } 387 } 388 389 String[] superContentTypesArray = superContentTypes.toArray(new String[superContentTypes.size()]); 390 String[] superMixinsArray = superMixins.toArray(new String[superMixins.size()]); 391 return Pair.of(superContentTypesArray, superMixinsArray); 392 } 393 394 /** 395 * Get plugin name for the given content type 396 * 397 * @param contentTypeId The content type id to test 398 * @return the plugin name 399 * @throws IllegalArgumentException if the content type does not exist. 400 */ 401 public String getPluginName(String contentTypeId) 402 { 403 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 404 if (cType == null) 405 { 406 throw new IllegalArgumentException("Unable to get plugin name of unknown content type '" + contentTypeId + "'"); 407 } 408 409 return cType.getPluginName(); 410 } 411 412 /** 413 * Builds the reverse hierarchies of ancestors of a content type 414 * @param contentTypeId The content type's id 415 * @return the reverse hierarchies with ancestors 416 */ 417 public List<Set<String>> buildReverseHierarchies(String contentTypeId) 418 { 419 Set<String> hierarchy = new LinkedHashSet<>(); 420 hierarchy.add(contentTypeId); 421 return _buildReverseHierarchies (contentTypeId, hierarchy); 422 } 423 424 private List<Set<String>> _buildReverseHierarchies(String contentTypeId, Set<String> hierarchy) 425 { 426 List<Set<String>> hierarchies = new ArrayList<>(); 427 428 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 429 if (cType == null) 430 { 431 throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'"); 432 } 433 434 String[] supertypeIds = cType.getSupertypeIds(); 435 if (supertypeIds.length > 0) 436 { 437 for (String superTypeId : supertypeIds) 438 { 439 Set<String> superHierarchy = new LinkedHashSet<>(hierarchy); 440 superHierarchy.add(superTypeId); 441 hierarchies.addAll(_buildReverseHierarchies(superTypeId, superHierarchy)); 442 } 443 } 444 else 445 { 446 hierarchies.add(hierarchy); 447 } 448 449 return hierarchies; 450 } 451 452 /** 453 * Get all views resulting of the concatenation of views of given content types and mixins. 454 * @param contentTypeIds the identifiers of the content types 455 * @param mixinIds the identifiers of the mixins 456 * @return The views 457 */ 458 public Map<String, View> getViews(String[] contentTypeIds, String[] mixinIds) 459 { 460 return getViews(contentTypeIds, mixinIds, Collections.emptySet()); 461 } 462 463 /** 464 * Get all views resulting of the concatenation of views of given content types and mixins. 465 * @param contentTypeIds the identifiers of the content types 466 * @param mixinIds the identifiers of the mixins 467 * @param viewNamesToAvoid names of views that should not be managed 468 * @return The views 469 */ 470 public Map<String, View> getViews(String[] contentTypeIds, String[] mixinIds, Set<String> viewNamesToAvoid) 471 { 472 Map<String, View> views = new HashMap<>(); 473 474 for (String contentTypeId : contentTypeIds) 475 { 476 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 477 for (String viewName : contentType.getViewNames()) 478 { 479 if (!viewNamesToAvoid.contains(viewName) && !views.containsKey(viewName)) 480 { 481 views.put(viewName, getView(viewName, contentTypeIds, mixinIds)); 482 } 483 } 484 } 485 486 return views; 487 } 488 489 /** 490 * Get the view for view resulting of the concatenation of views of the given content. 491 * @param viewName the name of the view to retrieve 492 * @param content the given content 493 * @return The view or null if none matches. 494 */ 495 public View getView(String viewName, Content content) 496 { 497 return getView(viewName, content.getTypes(), content.getMixinTypes()); 498 } 499 500 /** 501 * Get the view for view resulting of the concatenation of views of the given content types and mixins. 502 * @param viewName the name of the view to retrieve 503 * @param contentTypeIds the identifiers of the content types 504 * @param mixinIds the identifiers of the mixins 505 * @return The view or null if none matches. 506 */ 507 public View getView(String viewName, String[] contentTypeIds, String[] mixinIds) 508 { 509 CacheKey cacheKey = CacheKey.of(Set.of(contentTypeIds), Set.of(mixinIds), viewName); 510 return _getViewCache().get(cacheKey, __ -> _computeView(viewName, contentTypeIds, mixinIds)); 511 } 512 513 /** 514 * Get the view for view resulting for a given content 515 * @param viewName the name of the view to retrieve. If null or empty, fallback view will be used. 516 * @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. 517 * @param content the content 518 * @return The view or null if none matches. 519 */ 520 public View getViewWithFallback(String viewName, String fallbackViewName, Content content) 521 { 522 return getViewWithFallback(viewName, fallbackViewName, content.getTypes(), content.getMixinTypes()); 523 } 524 525 /** 526 * Get the view for view resulting of the concatenation of views of the given content types and mixins. 527 * @param viewName the name of the view to retrieve. If null or empty, fallback view will be used. 528 * @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. 529 * @param contentTypeIds the identifiers of the content types 530 * @param mixinIds the identifiers of the mixins 531 * @return The view or null if none matches. 532 */ 533 public View getViewWithFallback(String viewName, String fallbackViewName, String[] contentTypeIds, String[] mixinIds) 534 { 535 String usedFallbackViewName = fallbackViewName; 536 if (StringUtils.isBlank(fallbackViewName)) 537 { 538 usedFallbackViewName = "main"; 539 } 540 541 // Use the fallbackViewName if no viewName is provided 542 String usedViewName = viewName; 543 if (StringUtils.isBlank(usedViewName)) 544 { 545 usedViewName = usedFallbackViewName; 546 } 547 548 View view = getView(usedViewName, contentTypeIds, mixinIds); 549 550 if (view == null && !usedViewName.equals(usedFallbackViewName)) 551 { 552 view = getView(usedFallbackViewName, contentTypeIds, mixinIds); 553 } 554 555 return view; 556 } 557 558 /** 559 * Converts the view with the given name in a JSON Map 560 * @param contentTypeId the content type identifier 561 * @param viewName the name of the view to convert 562 * @param isEdition <code>true</code> if the JSON result is used for edition purposes (configure a form panel, ...) <code>false</code> otherwise 563 * @return the view as a JSON Map 564 * @throws ProcessingException if an error occurs when converting the view 565 */ 566 @Callable 567 public Map<String, Object> getViewAsJSON(String contentTypeId, String viewName, boolean isEdition) throws ProcessingException 568 { 569 Map<String, Object> json = new LinkedHashMap<>(); 570 571 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 572 json.put("contentType", Map.of( 573 "id", contentTypeId, 574 "label", contentType.getLabel(), 575 "defaultTitle", contentType.getDefaultTitle())); 576 577 View view = contentType.getView(viewName); 578 if (view != null) 579 { 580 json.put("view", view.toJSON(DefinitionContext.newInstance().withEdition(isEdition))); 581 } 582 else 583 { 584 if (getLogger().isWarnEnabled()) 585 { 586 getLogger().warn(String.format("Unknown view '%s' for content type '%s'", viewName, contentType.getId())); 587 } 588 } 589 590 return json; 591 } 592 593 /** 594 * Converts the title view in a JSON Map 595 * @param contentTypeId the content type identifier 596 * @param isEdition <code>true</code> if the JSON result is used for edition purposes (configure a form panel, ...) <code>false</code> otherwise 597 * @return the view as a JSON Map 598 * @throws ProcessingException if an error occurs when converting the view 599 */ 600 @Callable 601 public Map<String, Object> getTitleViewAsJSON(String contentTypeId, boolean isEdition) throws ProcessingException 602 { 603 Map<String, Object> json = new LinkedHashMap<>(); 604 605 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 606 json.put("contentType", Map.of( 607 "id", contentTypeId, 608 "label", contentType.getLabel(), 609 "defaultTitle", contentType.getDefaultTitle())); 610 611 View titleView = new View(); 612 613 ModelItem modelItem = contentType.getModelItem("title"); 614 ViewElement viewElement = new ViewElement(); 615 viewElement.setDefinition((ElementDefinition) modelItem); 616 titleView.addViewItem(viewElement); 617 618 json.put("view", titleView.toJSON(DefinitionContext.newInstance().withEdition(isEdition))); 619 return json; 620 } 621 622 private View _computeView(String viewName, String[] contentTypeIds, String[] mixinIds) 623 { 624 List<View> views = new ArrayList<>(); 625 for (String contentTypeId : contentTypeIds) 626 { 627 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 628 629 View view = contentType.getView(viewName); 630 if (view != null) 631 { 632 views.add(view); 633 } 634 } 635 636 for (String id : mixinIds) 637 { 638 ContentType mixin = _getContentTypeEP().getExtension(id); 639 640 View view = mixin.getView(viewName); 641 if (view != null) 642 { 643 views.add(view); 644 } 645 } 646 647 return joinViews(views); 648 } 649 650 /** 651 * Parses the view of the given content type from the configurations 652 * @param contentType the content type 653 * @param viewConfigurations the view's configurations, indexed by the content types defining this view 654 * @return the parsed view 655 * @throws ConfigurationException if the configuration is invalid 656 */ 657 public View parseView(ContentType contentType, Map<ContentType, Configuration> viewConfigurations) throws ConfigurationException 658 { 659 List<View> views = new ArrayList<>(); 660 for (Map.Entry<ContentType, Configuration> viewConfigurationById : viewConfigurations.entrySet()) 661 { 662 ContentType originalContentType = viewConfigurationById.getKey(); 663 Configuration viewConfiguration = viewConfigurationById.getValue(); 664 665 ViewParser parser; 666 if (ContentType.VIEW_TAG_NAME.equals(viewConfiguration.getName())) 667 { 668 parser = new ContentTypeViewParser(contentType, originalContentType); 669 } 670 else 671 { 672 parser = new ContentTypeViewParser(contentType, originalContentType, Optional.of(ContentType.GROUP_TAG_NAME_WITH_LEGACY_SYNTAX), Optional.of(ContentType.ATTRIBUTE_REF_TAG_NAME_WITH_LEGACY_SYNTAX), false); 673 } 674 try 675 { 676 LifecycleHelper.setupComponent(parser, getLogger(), _context, _smanager, null); 677 678 View view = parser.parseView(viewConfiguration); 679 views.add(view); 680 } 681 catch (Exception e) 682 { 683 throw new ConfigurationException("Unable to initialize the content view parser", e); 684 } 685 finally 686 { 687 LifecycleHelper.dispose(parser); 688 } 689 } 690 691 return joinViews(views); 692 } 693 694 /** 695 * Creates a view that is a jointure between all the given views 696 * @param views the views to join 697 * @return the joined view 698 */ 699 public View joinViews(List<View> views) 700 { 701 if (views.isEmpty()) 702 { 703 return null; 704 } 705 706 View joinView = new View(); 707 for (View view : views) 708 { 709 joinView.includeView(view); 710 711 if (joinView.getName() == null) 712 { 713 joinView.setName(view.getName()); 714 joinView.setLabel(view.getLabel()); 715 joinView.setDescription(view.getDescription()); 716 joinView.setInternal(view.isInternal()); 717 joinView.setIconGlyph(view.getIconGlyph()); 718 joinView.setIconDecorator(view.getIconDecorator()); 719 joinView.setSmallIcon(view.getSmallIcon()); 720 joinView.setMediumIcon(view.getMediumIcon()); 721 joinView.setLargeIcon(view.getLargeIcon()); 722 } 723 } 724 return joinView; 725 } 726 727 /** 728 * Get all configurations of the views resulting of the concatenation of views of given content types and mixins. 729 * @param contentTypeIds the identifiers of the content types 730 * @param mixinIds the identifiers of the mixins 731 * @return The views' configurations, indexed by view names and content type containing the configuration 732 */ 733 Map<String, Map<ContentType, Configuration>> getViewConfigurations(String[] contentTypeIds, String[] mixinIds) 734 { 735 Map<String, Map<ContentType, Configuration>> viewConfigurations = new HashMap<>(); 736 737 for (String contentTypeId : contentTypeIds) 738 { 739 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 740 for (String viewName : contentType.getViewNames()) 741 { 742 viewConfigurations.computeIfAbsent(viewName, name -> new LinkedHashMap<>()) 743 .putAll(getViewConfigurations(viewName, new String[] {contentTypeId}, new String[0])); 744 } 745 } 746 747 return viewConfigurations; 748 } 749 750 /** 751 * Get the configurations of the view resulting of the concatenation of views of given content types and mixins. 752 * @param viewName the name of the view 753 * @param contentTypeIds the identifiers of the content types 754 * @param mixinIds the identifiers of the mixins 755 * @return The views' configurations, indexed by content type containing the configuration 756 */ 757 Map<ContentType, Configuration> getViewConfigurations(String viewName, String[] contentTypeIds, String[] mixinIds) 758 { 759 Map<ContentType, Configuration> viewConfigurations = new LinkedHashMap<>(); 760 761 for (String contentTypeId : contentTypeIds) 762 { 763 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 764 contentType.getViewConfiguration(viewName) 765 .ifPresentOrElse(config -> viewConfigurations.put(contentType, config), 766 () -> viewConfigurations.putAll(getViewConfigurations(viewName, contentType.getSupertypeIds(), new String[0]))); 767 } 768 769 return viewConfigurations; 770 } 771 772 /** 773 * Get all metadata sets for view resulting of the concatenation of metadata 774 * sets of given content types and mixins. 775 * 776 * @param cTypes The id of content types 777 * @param mixins The id of mixins 778 * @param includeInternal <code>true</code> True to include internal 779 * metadata sets 780 * @return The metadata sets 781 * @deprecated Use {@link #getViews(String[], String[])} instead 782 */ 783 @Deprecated 784 public Map<String, MetadataSet> getMetadataSetsForView(String[] cTypes, String[] mixins, boolean includeInternal) 785 { 786 Map<String, MetadataSet> metadataSets = new HashMap<>(); 787 788 Set<String> viewMetadataSetNames = new HashSet<>(); 789 for (String id : cTypes) 790 { 791 ContentType cType = _getContentTypeEP().getExtension(id); 792 for (String name : cType.getViewMetadataSetNames(includeInternal)) 793 { 794 if (!viewMetadataSetNames.contains(name)) 795 { 796 viewMetadataSetNames.add(name); 797 } 798 } 799 } 800 801 for (String viewMetadataSetName : viewMetadataSetNames) 802 { 803 metadataSets.put(viewMetadataSetName, getMetadataSetForView(viewMetadataSetName, cTypes, mixins)); 804 } 805 806 return metadataSets; 807 } 808 809 /** 810 * Get all metadata sets for edition resulting of the concatenation of 811 * metadata sets of given content types and mixins. 812 * 813 * @param cTypes The id of content types 814 * @param mixins The id of mixins 815 * @param includeInternal <code>true</code> True to include internal 816 * metadata sets 817 * @return The metadata sets 818 * @deprecated Use {@link #getViews(String[], String[])} instead 819 */ 820 @Deprecated 821 public Map<String, MetadataSet> getMetadataSetsForEdition(String[] cTypes, String[] mixins, boolean includeInternal) 822 { 823 Map<String, MetadataSet> metadataSets = new HashMap<>(); 824 825 Set<String> editionMetadataSetNames = new HashSet<>(); 826 for (String id : cTypes) 827 { 828 ContentType cType = _getContentTypeEP().getExtension(id); 829 for (String name : cType.getEditionMetadataSetNames(includeInternal)) 830 { 831 if (!editionMetadataSetNames.contains(name)) 832 { 833 editionMetadataSetNames.add(name); 834 } 835 } 836 } 837 838 for (String editionMetadataSetName : editionMetadataSetNames) 839 { 840 metadataSets.put(editionMetadataSetName, getMetadataSetForEdition(editionMetadataSetName, cTypes, mixins)); 841 } 842 843 return metadataSets; 844 } 845 846 /** 847 * Get the metadata set for view resulting of the concatenation of metadata 848 * sets of given content types and mixins. 849 * 850 * @param metadataSetName the name of metadata set to retrieve 851 * @param cTypes The id of content types 852 * @param mixins The id of mixins 853 * @return The list of metadata set names. 854 * @deprecated Use {@link #getView(String, String[], String[])} instead 855 */ 856 @Deprecated 857 public MetadataSet getMetadataSetForView(String metadataSetName, String[] cTypes, String[] mixins) 858 { 859 MetadataSet joinMetadataSet = _getCacheForView().get( 860 CacheKey.of(Set.of(cTypes), Set.of(mixins), metadataSetName), 861 __ -> _computeMetadataSetForView(metadataSetName, cTypes, mixins)); 862 return joinMetadataSet; 863 } 864 865 @Deprecated 866 private MetadataSet _computeMetadataSetForView(String metadataSetName, String[] cTypes, String[] mixins) 867 { 868 MetadataSet joinMetadataSet = new MetadataSet(); 869 boolean foundOne = false; 870 871 for (String id : cTypes) 872 { 873 ContentType cType = _getContentTypeEP().getExtension(id); 874 875 MetadataSet metadataSet = cType.getMetadataSetForView(metadataSetName); 876 if (metadataSet != null) 877 { 878 foundOne = true; 879 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 880 881 if (joinMetadataSet.getName() == null) 882 { 883 joinMetadataSet.setName(metadataSetName); 884 joinMetadataSet.setLabel(metadataSet.getLabel()); 885 joinMetadataSet.setDescription(metadataSet.getDescription()); 886 joinMetadataSet.setIconGlyph(metadataSet.getIconGlyph()); 887 joinMetadataSet.setIconDecorator(metadataSet.getIconDecorator()); 888 joinMetadataSet.setSmallIcon(metadataSet.getSmallIcon()); 889 joinMetadataSet.setMediumIcon(metadataSet.getMediumIcon()); 890 joinMetadataSet.setLargeIcon(metadataSet.getLargeIcon()); 891 joinMetadataSet.setEdition(false); 892 } 893 } 894 } 895 896 for (String id : mixins) 897 { 898 ContentType mixin = _getContentTypeEP().getExtension(id); 899 900 MetadataSet metadataSet = mixin.getMetadataSetForView(metadataSetName); 901 if (metadataSet != null) 902 { 903 foundOne = true; 904 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 905 } 906 } 907 908 if (!foundOne) 909 { 910 return null; 911 } 912 913 return joinMetadataSet; 914 } 915 916 /** 917 * Get the metadata set for edition resulting of the concatenation of 918 * metadata sets of given content types and mixins. 919 * 920 * @param metadataSetName the name of metadata set to retrieve 921 * @param cTypes The id of content types 922 * @param mixins The id of mixins 923 * @return The metadata set 924 * @deprecated Use {@link #getView(String, String[], String[])} instead 925 */ 926 @Deprecated 927 public MetadataSet getMetadataSetForEdition(String metadataSetName, String[] cTypes, String[] mixins) 928 { 929 MetadataSet joinMetadataSet = _getCacheForEdition().get( 930 CacheKey.of(Set.of(cTypes), Set.of(mixins), metadataSetName), 931 __ -> _computeMetadataSetForEdition(metadataSetName, cTypes, mixins)); 932 return joinMetadataSet; 933 } 934 935 @Deprecated 936 private MetadataSet _computeMetadataSetForEdition(String metadataSetName, String[] cTypes, String[] mixins) 937 { 938 MetadataSet joinMetadataSet = new MetadataSet(); 939 boolean foundOne = false; 940 941 for (String id : cTypes) 942 { 943 ContentType cType = _getContentTypeEP().getExtension(id); 944 945 MetadataSet metadataSet = cType.getMetadataSetForEdition(metadataSetName); 946 if (metadataSet != null) 947 { 948 foundOne = true; 949 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 950 951 if (joinMetadataSet.getName() == null) 952 { 953 joinMetadataSet.setName(metadataSetName); 954 joinMetadataSet.setLabel(metadataSet.getLabel()); 955 joinMetadataSet.setDescription(metadataSet.getDescription()); 956 joinMetadataSet.setIconGlyph(metadataSet.getIconGlyph()); 957 joinMetadataSet.setIconDecorator(metadataSet.getIconDecorator()); 958 joinMetadataSet.setSmallIcon(metadataSet.getSmallIcon()); 959 joinMetadataSet.setMediumIcon(metadataSet.getMediumIcon()); 960 joinMetadataSet.setLargeIcon(metadataSet.getLargeIcon()); 961 joinMetadataSet.setEdition(true); 962 } 963 } 964 } 965 966 for (String id : mixins) 967 { 968 ContentType mixin = _getContentTypeEP().getExtension(id); 969 970 MetadataSet metadataSet = mixin.getMetadataSetForEdition(metadataSetName); 971 if (metadataSet != null) 972 { 973 foundOne = true; 974 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 975 } 976 } 977 978 if (!foundOne) 979 { 980 return null; 981 } 982 983 return joinMetadataSet; 984 } 985 986 /** 987 * Get the views of a content type 988 * @param contentTypeId the content type id 989 * @param includeInternals Set to true to include internal views. 990 * @return the views' info 991 */ 992 public List<Map<String, Object>> getViewsInfo(String contentTypeId, boolean includeInternals) 993 { 994 List<Map<String, Object>> views = new ArrayList<>(); 995 996 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 997 998 Set<String> viewNames = contentType.getViewNames(includeInternals); 999 for (String viewName : viewNames) 1000 { 1001 View view = contentType.getView(viewName); 1002 1003 Map<String, Object> viewInfos = new HashMap<>(); 1004 viewInfos.put("name", viewName); 1005 viewInfos.put("label", view.getLabel()); 1006 viewInfos.put("description", view.getDescription()); 1007 views.add(viewInfos); 1008 } 1009 1010 return views; 1011 } 1012 1013 /** 1014 * Get the indexing model resulting of the concatenation of indexing models of given content types and mixins. 1015 * @param content The content. 1016 * @return The indexing model 1017 */ 1018 public IndexingModel getIndexingModel(Content content) 1019 { 1020 return getIndexingModel(content.getTypes(), content.getMixinTypes()); 1021 } 1022 1023 /** 1024 * Get the indexing model resulting of the concatenation of indexing models of given content types and mixins. 1025 * @param cTypes The id of content types 1026 * @param mixins The id of mixins 1027 * @return The indexing model 1028 */ 1029 public IndexingModel getIndexingModel(String[] cTypes, String[] mixins) 1030 { 1031 IndexingModel joinIndexingModel = new IndexingModel(); 1032 1033 for (String id : cTypes) 1034 { 1035 ContentType cType = _getContentTypeEP().getExtension(id); 1036 1037 if (cType != null) 1038 { 1039 IndexingModel indexingModel = cType.getIndexingModel(); 1040 1041 for (IndexingField field : indexingModel.getFields()) 1042 { 1043 joinIndexingModel.addIndexingField(field); 1044 } 1045 } 1046 else 1047 { 1048 if (getLogger().isWarnEnabled()) 1049 { 1050 getLogger().warn(String.format("Trying to get indexing model for an unknown content type : '%s'.", id)); 1051 } 1052 } 1053 } 1054 1055 for (String id : mixins) 1056 { 1057 ContentType mixin = _getContentTypeEP().getExtension(id); 1058 1059 if (mixin != null) 1060 { 1061 IndexingModel indexingModel = mixin.getIndexingModel(); 1062 1063 for (IndexingField field : indexingModel.getFields()) 1064 { 1065 joinIndexingModel.addIndexingField(field); 1066 } 1067 } 1068 else 1069 { 1070 if (getLogger().isWarnEnabled()) 1071 { 1072 getLogger().warn(String.format("Trying to get indexing model for an unknown mixin type : '%s'.", id)); 1073 } 1074 } 1075 } 1076 1077 return joinIndexingModel; 1078 } 1079 1080 1081 /** 1082 * Copy the elements of metadata set into a metadata set of destination, 1083 * only if the elements are not already presents 1084 * 1085 * @param src The metadata set to copy 1086 * @param dest The metadata of destination 1087 * @deprecated Use {@link View#includeView(View)} instead 1088 */ 1089 @Deprecated 1090 public void copyMetadataSetElementsIfNotExist(AbstractMetadataSetElement src, AbstractMetadataSetElement dest) 1091 { 1092 _copyMetadataSetElementsIfNotExist(src, dest, null); 1093 } 1094 1095 /** 1096 * Copy the elements of the source to the destination if they do not exist in the reference 1097 * @param src the source 1098 * @param dest the destination 1099 * @param reference the reference 1100 * @deprecated Use {@link View#includeView(View)} instead 1101 */ 1102 @Deprecated 1103 private void _copyMetadataSetElementsIfNotExist(AbstractMetadataSetElement src, AbstractMetadataSetElement dest, AbstractMetadataSetElement reference) 1104 { 1105 AbstractMetadataSetElement root = reference == null ? dest : reference; 1106 1107 for (AbstractMetadataSetElement elmt : src.getElements()) 1108 { 1109 if (elmt instanceof MetadataDefinitionReference) 1110 { 1111 String metadataName = ((MetadataDefinitionReference) elmt).getMetadataName(); 1112 if (!root.hasMetadataDefinitionReference(metadataName)) 1113 { 1114 dest.addElement(elmt); 1115 } 1116 } 1117 else if (elmt instanceof Fieldset) 1118 { 1119 Fieldset fieldset = new Fieldset(); 1120 fieldset.setLabel(((Fieldset) elmt).getLabel()); 1121 fieldset.setRole(((Fieldset) elmt).getRole()); 1122 1123 _copyMetadataSetElementsIfNotExist(elmt, fieldset, root); 1124 1125 dest.addElement(fieldset); 1126 } 1127 } 1128 } 1129 1130 /** 1131 * Retrieve the metadata definition represented by the given path. 1132 * The path can represent a metadata on another content. 1133 * @param metadataPath the metadata path separated by '/' 1134 * @param content The content. 1135 * @return the metadata definition or null. 1136 * @deprecated Use {@link Content#getDefinition(String)} API instead 1137 */ 1138 @Deprecated 1139 public MetadataDefinition getMetadataDefinition(String metadataPath, Content content) 1140 { 1141 List<MetadataDefinition> metadataDefinitionsByPath = _getMetadataDefinitionPath(metadataPath, content); 1142 return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null; 1143 } 1144 1145 /** 1146 * Retrieve the list of successive metadata definitions represented by the given path. 1147 * The path can represent a metadata on another content. 1148 * @param metadataPath the metadata path separated by '/' 1149 * @param content The content. 1150 * @return the list of metadata definitions, one by path element. 1151 * @deprecated Use {@link ContentTypesHelper#getModelItemPath(String, Content)} instead 1152 */ 1153 @Deprecated 1154 private List<MetadataDefinition> _getMetadataDefinitionPath(String metadataPath, Content content) 1155 { 1156 return _getMetadataDefinitionPath(metadataPath, content.getTypes(), content.getMixinTypes()); 1157 } 1158 1159 /** 1160 * Retrieve the metadata definition represented by the given path. 1161 * The path can represent a metadata on another content. 1162 * @param metadataPath the metadata path separated by '/' 1163 * @param cTypes The content types. 1164 * @param mixins The content mixins types. 1165 * @return the metadata definition or null. 1166 * @deprecated Use {@link ContentTypesHelper#getModelItem(String, String[], String[])} instead 1167 */ 1168 @Deprecated 1169 public MetadataDefinition getMetadataDefinition(String metadataPath, String[] cTypes, String[] mixins) 1170 { 1171 List<MetadataDefinition> metadataDefinitionsByPath = _getMetadataDefinitionPath(metadataPath, cTypes, mixins); 1172 return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null; 1173 } 1174 1175 /** 1176 * Retrieves the model item at the given path 1177 * @param path path of the model item to retrieve. No matter if it is a definition or data path (with repeater entry positions) 1178 * @param cTypes identifiers of the content types where to search the model item 1179 * @param mixins identifiers of the mixins where to search the model item 1180 * @return the model item 1181 * @throws IllegalArgumentException if the given path is null or empty 1182 * @throws UndefinedItemPathException if there is no item defined at the given path in given item containers 1183 */ 1184 public ModelItem getModelItem(String path, String[] cTypes, String[] mixins) throws IllegalArgumentException, UndefinedItemPathException 1185 { 1186 Collection<ContentType> contentTypes = new ArrayList<>(); 1187 1188 String[] allContentTypes = ArrayUtils.addAll(cTypes, mixins); 1189 for (String contentTypeId : allContentTypes) 1190 { 1191 if (_getContentTypeEP().hasExtension(contentTypeId)) 1192 { 1193 contentTypes.add(_getContentTypeEP().getExtension(contentTypeId)); 1194 } 1195 else 1196 { 1197 if (getLogger().isWarnEnabled()) 1198 { 1199 getLogger().warn("Unknown content type identifier : " + contentTypeId); 1200 } 1201 } 1202 } 1203 1204 return ModelHelper.getModelItem(path, contentTypes); 1205 } 1206 1207 /** 1208 * Retrieve the list of successive metadata definitions represented by the given path. 1209 * The path can represent a metadata on another content. 1210 * @param metadataPath the metadata path separated by '/' 1211 * @param cTypes The id of content types 1212 * @param mixins The id of mixins 1213 * @return the metadata definition or <code>null</code> if not found 1214 * @deprecated Use {@link ContentTypesHelper#getModelItemPath(String, Content)} instead 1215 */ 1216 @Deprecated 1217 private List<MetadataDefinition> _getMetadataDefinitionPath(String metadataPath, String[] cTypes, String[] mixins) 1218 { 1219 String[] allContentTypes = ArrayUtils.addAll(cTypes, mixins); 1220 1221 for (String cTypeId : allContentTypes) 1222 { 1223 ContentType cType = _getContentTypeEP().getExtension(cTypeId); 1224 if (cType != null) 1225 { 1226 List<MetadataDefinition> metaDefs = _getMetadataDefinitionPath(metadataPath, cType); 1227 if (!metaDefs.isEmpty()) 1228 { 1229 return metaDefs; 1230 } 1231 } 1232 else 1233 { 1234 if (getLogger().isWarnEnabled()) 1235 { 1236 getLogger().warn("Unknown content type identifier : " + cTypeId); 1237 } 1238 } 1239 } 1240 1241 return Collections.emptyList(); 1242 } 1243 1244 /** 1245 * Retrieves a metadata definition from a path. The metadata can be defined in a referenced or sub content. 1246 * @param metadataPath the metadata path separated by '/' 1247 * @param initialContentType The initial content type to start the search 1248 * @return the metadata definition or <code>null</code> if not found 1249 * @deprecated Use {@link ContentType#getModelItem(String)} instead 1250 */ 1251 @Deprecated 1252 public MetadataDefinition getMetadataDefinition(String metadataPath, ContentType initialContentType) 1253 { 1254 List<MetadataDefinition> metadataDefinitionsByPath = _getMetadataDefinitionPath(metadataPath, initialContentType); 1255 return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null; 1256 } 1257 1258 /** 1259 * Retrieve the list of successive metadata definitions represented by the given path. 1260 * The path can represent a metadata on another content. 1261 * @param initialContentType The initial content type to start the search 1262 * @param metadataPath the metadata path separated by '/' 1263 * @return the list of metadata definitions, one by path element. 1264 * @deprecated Use {@link ContentTypesHelper#getModelItemPath(String, ContentType)} instead 1265 */ 1266 @Deprecated 1267 private List<MetadataDefinition> _getMetadataDefinitionPath(String metadataPath, ContentType initialContentType) 1268 { 1269 List<MetadataDefinition> definitions = new ArrayList<>(); 1270 1271 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 1272 1273 if (pathSegments.length > 0) 1274 { 1275 MetadataDefinition metadataDef = initialContentType.getMetadataDefinition(pathSegments[0]); 1276 1277 if (metadataDef != null) 1278 { 1279 definitions.add(metadataDef); 1280 } 1281 else 1282 { 1283 return Collections.emptyList(); 1284 } 1285 1286 for (int i = 1; i < pathSegments.length; i++) 1287 { 1288 if (metadataDef.getType() == MetadataType.CONTENT || metadataDef.getType() == MetadataType.SUB_CONTENT) 1289 { 1290 String refCTypeId = metadataDef.getContentType(); 1291 if (refCTypeId != null && _getContentTypeEP().hasExtension(refCTypeId)) 1292 { 1293 ContentType refCType = _getContentTypeEP().getExtension(refCTypeId); 1294 1295 List<MetadataDefinition> followingDefs = _getMetadataDefinitionPath(StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, i, pathSegments.length), refCType); 1296 if (CollectionUtils.isEmpty(followingDefs)) 1297 { 1298 return Collections.emptyList(); 1299 } 1300 definitions.addAll(followingDefs); 1301 1302 return definitions; 1303 } 1304 else if ("title".equals(pathSegments[i])) 1305 { 1306 // No specific content type: allow only title. 1307 definitions.add(getTitleMetadataDefinition()); 1308 return definitions; 1309 } 1310 else 1311 { 1312 return Collections.emptyList(); 1313 } 1314 } 1315 else 1316 { 1317 metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); 1318 if (metadataDef != null) 1319 { 1320 definitions.add(metadataDef); 1321 } 1322 else 1323 { 1324 return Collections.emptyList(); 1325 } 1326 } 1327 } 1328 } 1329 1330 return definitions; 1331 } 1332 1333 /** 1334 * Retrieve the list of successive model items represented by the given paths, indexed by path. 1335 * @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) 1336 * @param content the content 1337 * @return the list of successive model items, indexed by path 1338 * @throws IllegalArgumentException if one of the given paths is null or empty 1339 * @throws UndefinedItemPathException if there is no item defined at one of the given paths in given item containers 1340 */ 1341 public Map<String, List<ModelItem>> getModelItemsPaths(Set<String> paths, Content content) 1342 { 1343 return ModelHelper.getAllModelItemsInPaths(paths, content.getModel()); 1344 } 1345 1346 /** 1347 * Retrieve the list of successive model items represented by the given path. 1348 * @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) 1349 * @param content the content 1350 * @return the list of successive model items 1351 * @throws IllegalArgumentException if the given path is null or empty 1352 * @throws UndefinedItemPathException if there is no item defined at the given path in given item containers 1353 */ 1354 public List<ModelItem> getModelItemPath(String path, Content content) throws IllegalArgumentException, UndefinedItemPathException 1355 { 1356 return ModelHelper.getAllModelItemsInPath(path, content.getModel()); 1357 } 1358 1359 /** 1360 * Retrieve the list of successive model items represented by the given paths, indexed by path. 1361 * @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) 1362 * @param contentType the content type 1363 * @return the list of successive model items, indexed by path 1364 * @throws IllegalArgumentException if one of the given paths is null or empty 1365 * @throws UndefinedItemPathException if there is no item defined at one of the given paths in given item containers 1366 */ 1367 public Map<String, List<ModelItem>> getModelItemsPaths(Set<String> paths, ContentType contentType) 1368 { 1369 return ModelHelper.getAllModelItemsInPaths(paths, Collections.singleton(contentType)); 1370 } 1371 1372 /** 1373 * Retrieve the list of successive model items represented by the given path. 1374 * @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) 1375 * @param contentType the content type 1376 * @return the list of successive model items 1377 * @throws IllegalArgumentException if the given path is null or empty 1378 * @throws UndefinedItemPathException if there is no item defined at the given path in given item containers 1379 */ 1380 public List<ModelItem> getModelItemPath(String path, ContentType contentType) 1381 { 1382 return ModelHelper.getAllModelItemsInPath(path, Collections.singleton(contentType)); 1383 } 1384 1385 /** 1386 * Determines if the given content type can be added to content 1387 * 1388 * @param content The content 1389 * @param cTypeId The id of content type 1390 * @return <code>true</code> if the content type is compatible with content 1391 */ 1392 public boolean isCompatibleContentType(Content content, String cTypeId) 1393 { 1394 String[] currentContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 1395 1396 ArrayList<String> cTypes = new ArrayList<>(Arrays.asList(currentContentTypes)); 1397 cTypes.add(cTypeId); 1398 1399 try 1400 { 1401 getModelItems(cTypes.toArray(new String[cTypes.size()])); 1402 return true; 1403 } 1404 catch (ConfigurationException e) 1405 { 1406 return false; 1407 } 1408 } 1409 1410 /** 1411 * Retrieves all model items of given content types. 1412 * @param contentTypes The identifier of the content types 1413 * @return the model items 1414 * @throws ConfigurationException if an error occurred 1415 */ 1416 public Map<String, ModelItem> getModelItems(String[] contentTypes) throws ConfigurationException 1417 { 1418 Map<String, ModelItem> items = new LinkedHashMap<>(); 1419 1420 for (String id : contentTypes) 1421 { 1422 ContentType contentType = _getContentTypeEP().getExtension(id); 1423 1424 for (ModelItem currentItem : contentType.getModelItems()) 1425 { 1426 final String currentItemName = currentItem.getName(); 1427 if (items.containsKey(currentItemName)) 1428 { 1429 ModelItem existingItem = items.get(currentItemName); 1430 1431 if (!currentItem.getModel().equals(existingItem.getModel())) 1432 { 1433 // The definition does not provide from a common ancestor 1434 throw new ConfigurationException("The metadata '" + currentItemName + "' defined in content-type '" + id + "' is already defined in another co-super-type '" 1435 + existingItem.getModel() + "'"); 1436 } 1437 continue; 1438 } 1439 1440 items.put(currentItemName, currentItem); 1441 } 1442 } 1443 1444 return items; 1445 } 1446 1447 /** 1448 * Retrieves the common model items for a list of content types 1449 * @param contentTypeIds The list of content types to consider 1450 * @param viewName The view name to list model items 1451 * @return The map of model items. Key are the model item's path in the content type 1452 */ 1453 public Map<String, ModelItem> getCommonModelItems(Collection<String> contentTypeIds, String viewName) 1454 { 1455 Map<String, ModelItem> commonModelItems = null; 1456 1457 for (String contentTypeId : contentTypeIds) 1458 { 1459 ContentType contentType = _getContentTypeEP().getExtension(contentTypeId); 1460 View view = contentType.getView(viewName); 1461 1462 Map<String, ModelItem> accumulator = new LinkedHashMap<>(); 1463 if (view != null) 1464 { 1465 _populateModelItemsAccumulator(accumulator, view, StringUtils.EMPTY); 1466 } 1467 1468 if (commonModelItems == null) 1469 { 1470 commonModelItems = new LinkedHashMap<>(); 1471 for (Map.Entry<String, ModelItem> accumulatorEntry : accumulator.entrySet()) 1472 { 1473 commonModelItems.put(accumulatorEntry.getKey(), accumulatorEntry.getValue()); 1474 } 1475 } 1476 else 1477 { 1478 // only retains common attributes (performs a set intersection) 1479 commonModelItems.keySet().retainAll(accumulator.keySet()); 1480 } 1481 } 1482 1483 return commonModelItems != null ? commonModelItems : Collections.emptyMap(); 1484 } 1485 1486 /** 1487 * Populates the accumulator with the model items 1488 * @param internalAcc the accumulator of model items 1489 * @param viewItemContainer the view item container of the content 1490 * @param prefix the view item accessor path prefix 1491 */ 1492 protected void _populateModelItemsAccumulator(Map<String, ModelItem> internalAcc, ViewItemContainer viewItemContainer, String prefix) 1493 { 1494 for (ViewItem viewItem : viewItemContainer.getViewItems()) 1495 { 1496 String newPrefix = prefix; 1497 1498 if (viewItem instanceof ModelViewItem) 1499 { 1500 ModelItem modelItem = ((ModelViewItem) viewItem).getDefinition(); 1501 if (modelItem != null) 1502 { 1503 internalAcc.put(modelItem.getPath(), modelItem); 1504 1505 if (viewItem instanceof ViewItemContainer) 1506 { 1507 newPrefix += ModelItem.ITEM_PATH_SEPARATOR + modelItem.getName(); 1508 } 1509 } 1510 } 1511 1512 if (viewItem instanceof ViewItemContainer) 1513 { 1514 _populateModelItemsAccumulator(internalAcc, (ViewItemContainer) viewItem, newPrefix); 1515 } 1516 } 1517 } 1518 1519 /** 1520 * Get information on content types.<br> 1521 * @return A Map with content types 1522 */ 1523 public Set<Map<String, Object>> getContentTypesInformations() 1524 { 1525 // Collect content types. 1526 List<String> allContentTypesIds = _getContentTypeEP() 1527 .getExtensionsIds() 1528 .stream() 1529 .collect(Collectors.toList()); 1530 return getContentTypesInformations(allContentTypesIds, true); 1531 } 1532 1533 /** 1534 * Get the content types information as JSON 1535 * @param contentTypeIds the list of content type ids 1536 * @param withRight true to add rights to results 1537 * @return the list of content types information as JSON 1538 */ 1539 @Callable 1540 public Set<Map<String, Object>> getContentTypesInformations(List<String> contentTypeIds, boolean withRight) 1541 { 1542 Set<Map<String, Object>> results = new HashSet<>(); 1543 for (String contentTypeId : contentTypeIds) 1544 { 1545 ContentType cType = _cTypeEP.getExtension(contentTypeId); 1546 if (cType != null) 1547 { 1548 Map<String, Object> contentTypeProperties = getContentTypeProperties(cType); 1549 if (withRight) 1550 { 1551 contentTypeProperties.put("rightEvaluated", _hasRight(cType)); 1552 } 1553 results.add(contentTypeProperties); 1554 } 1555 } 1556 1557 return results; 1558 } 1559 1560 /** 1561 * Get information on content types.<br> 1562 * 1563 * @param ids The id of content types to retrieve 1564 * @param inherited If true, the sub-types will be also returned. 1565 * @param checkRights If true, only content types allowed for creation will 1566 * be returned 1567 * @param includePrivate If true, the list will include the private content types. By default the list is restricted to the public content types. 1568 * @param includeMixins If true the list will include the mixins content types. 1569 * @param includeAbstract If true the list will include the abstract content types. 1570 * @return A Map with retrieved, unknown, not-allowed and private content types 1571 */ 1572 @Callable 1573 public Map<String, Object> getContentTypesList (List<String> ids, boolean inherited, boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract) 1574 { 1575 // Collect content types. 1576 Collection<String> allContentTypesIds = new ArrayList<>(); 1577 if (ids == null || ids.isEmpty()) 1578 { 1579 allContentTypesIds = _getContentTypeEP().getExtensionsIds(); 1580 } 1581 else 1582 { 1583 for (String id : ids) 1584 { 1585 _addIfNotPresent(allContentTypesIds, id); 1586 1587 if (inherited) 1588 { 1589 for (String subTypeId : _getContentTypeEP().getSubTypes(id)) 1590 { 1591 _addIfNotPresent(allContentTypesIds, subTypeId); 1592 } 1593 } 1594 } 1595 } 1596 1597 // Resolve and organize content types 1598 return _dispatchContentTypes(checkRights, includePrivate, includeMixins, includeAbstract, allContentTypesIds); 1599 } 1600 1601 private Map<String, Object> _dispatchContentTypes(boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract, Collection<String> allContentTypesIds) 1602 { 1603 List<Map<String, Object>> contentTypes = new ArrayList<>(); 1604 List<String> unknownContentTypes = new ArrayList<>(); 1605 List<String> noRightContentTypes = new ArrayList<>(); 1606 List<String> privateContentTypes = new ArrayList<>(); 1607 List<String> mixinContentTypes = new ArrayList<>(); 1608 List<String> abstractContentTypes = new ArrayList<>(); 1609 1610 for (String id : allContentTypesIds) 1611 { 1612 ContentType cType = _getContentTypeEP().getExtension(id); 1613 1614 if (cType != null) 1615 { 1616 if (cType.isAbstract() && !includeAbstract) 1617 { 1618 abstractContentTypes.add(id); 1619 } 1620 else if (cType.isPrivate() && !includePrivate) 1621 { 1622 privateContentTypes.add(id); 1623 } 1624 else if (cType.isMixin() && !includeMixins) 1625 { 1626 mixinContentTypes.add(id); 1627 } 1628 else if (!checkRights || _hasRight(cType)) 1629 { 1630 contentTypes.add(getContentTypeProperties(cType)); 1631 } 1632 else 1633 { 1634 noRightContentTypes.add(id); 1635 } 1636 } 1637 else 1638 { 1639 unknownContentTypes.add(id); 1640 } 1641 } 1642 1643 Map<String, Object> result = new HashMap<>(); 1644 1645 result.put("contentTypes", contentTypes); 1646 result.put("noRightContentTypes", noRightContentTypes); 1647 result.put("unknownContentTypes", unknownContentTypes); 1648 result.put("privateContentTypes", privateContentTypes); 1649 result.put("mixinContentTypes", mixinContentTypes); 1650 result.put("abstractContentTypes", abstractContentTypes); 1651 1652 return result; 1653 } 1654 1655 /** 1656 * Get the content type properties 1657 * 1658 * @param contentType The content type 1659 * @return The content type properties 1660 */ 1661 public Map<String, Object> getContentTypeProperties(ContentType contentType) 1662 { 1663 Map<String, Object> infos = new HashMap<>(); 1664 1665 infos.put("id", contentType.getId()); 1666 infos.put("label", contentType.getLabel()); 1667 infos.put("description", contentType.getDescription()); 1668 infos.put("defaultTitle", contentType.getDefaultTitle()); 1669 infos.put("iconGlyph", contentType.getIconGlyph()); 1670 infos.put("iconDecorator", contentType.getIconDecorator()); 1671 infos.put("iconSmall", contentType.getSmallIcon()); 1672 infos.put("iconMedium", contentType.getMediumIcon()); 1673 infos.put("iconLarge", contentType.getLargeIcon()); 1674 infos.put("right", contentType.getRight()); 1675 infos.put("isMultilingual", contentType.isMultilingual()); 1676 infos.put("isSimple", contentType.isSimple()); 1677 infos.put("isPrivate", contentType.isPrivate()); 1678 infos.put("isAbstract", contentType.isAbstract()); 1679 infos.put("isReferenceTable", contentType.isReferenceTable()); 1680 infos.put("isMixin", contentType.isMixin()); 1681 infos.put("superTypes", contentType.getSupertypeIds()); 1682 infos.put("tags", contentType.getTags()); 1683 infos.put("parentMetadataName", contentType.getParentAttributeDefinition() 1684 .map(ContentAttributeDefinition::getPath) 1685 .orElse(StringUtils.EMPTY)); 1686 infos.put("editionMetatadaSets", contentType.getEditionMetadataSetNames(true)); 1687 1688 return infos; 1689 } 1690 1691 /** 1692 * Test if the current user has the right needed by the content type to create a content. 1693 * @param contentTypeId The content type id 1694 * @return true if the user has the right needed, false otherwise. 1695 */ 1696 public boolean hasRight(String contentTypeId) 1697 { 1698 return Optional.ofNullable(contentTypeId) 1699 .filter(_getContentTypeEP()::hasExtension) 1700 .map(_getContentTypeEP()::getExtension) 1701 .map(this::_hasRight) 1702 .orElse(false); 1703 } 1704 1705 /** 1706 * Test if the current user has the right needed by the content type to create a content. 1707 * @param contentType The content type 1708 * @return true if the user has the right needed, false otherwise. 1709 */ 1710 protected boolean _hasRight(ContentType contentType) 1711 { 1712 boolean hasRight = false; 1713 1714 String right = contentType.getRight(); 1715 1716 if (right == null) 1717 { 1718 hasRight = true; 1719 } 1720 else 1721 { 1722 UserIdentity user = _userProvider.getUser(); 1723 hasRight = _rightManager.hasRight(user, right, "/cms") == RightResult.RIGHT_ALLOW || _rightManager.hasRight(user, right, _rootContentHelper.getRootContent()) == RightResult.RIGHT_ALLOW; 1724 } 1725 1726 return hasRight; 1727 } 1728 1729 private void _addIfNotPresent(Collection<String> collection, String value) 1730 { 1731 if (!collection.contains(value)) 1732 { 1733 collection.add(value); 1734 } 1735 } 1736 1737 /** 1738 * Determine whether a metadata can be read at this time. 1739 * 1740 * @param metadataDef the metadata definition 1741 * @param content The content where metadata is to be read on. 1742 * @return <code>true</code> if the current user is allowed to read the 1743 * metadata of this content. 1744 * @throws AmetysRepositoryException if an error occurs while accessing the 1745 * content. 1746 * @deprecated Use {@link AttributeDefinition#canRead(Content)} instead 1747 */ 1748 @Deprecated 1749 public boolean canRead(Content content, MetadataDefinition metadataDef) 1750 { 1751 String id = metadataDef.getReferenceContentType(); 1752 ContentType cType = _getContentTypeEP().getExtension(id); 1753 return cType.canRead(content, metadataDef); 1754 } 1755 1756 /** 1757 * Determine whether a metadata can be read at this time. 1758 * 1759 * @param metadataDef the metadata definition 1760 * @param content The content where metadata is to be read on. 1761 * @return <code>true</code> if the current user is allowed to read the 1762 * metadata of this content. 1763 * @throws AmetysRepositoryException if an error occurs while accessing the 1764 * content. 1765 * @deprecated Use {@link AttributeDefinition#canWrite(Content)} instead 1766 */ 1767 @Deprecated 1768 public boolean canWrite(Content content, MetadataDefinition metadataDef) 1769 { 1770 String id = metadataDef.getReferenceContentType(); 1771 ContentType cType = _getContentTypeEP().getExtension(id); 1772 return cType.canWrite(content, metadataDef); 1773 } 1774 1775 /** 1776 * Get the id of content type to use for rendering 1777 * 1778 * @param content The content 1779 * @return the id of dynamic or standard content type 1780 */ 1781 public String getContentTypeIdForRendering(Content content) 1782 { 1783 String dynamicContentTypeId = getDynamicContentTypeId(content.getTypes(), content.getMixinTypes()); 1784 if (dynamicContentTypeId != null) 1785 { 1786 return dynamicContentTypeId; 1787 } 1788 1789 ContentType firstContentType = getFirstContentType(content); 1790 return firstContentType != null ? firstContentType.getId() : StringUtils.EMPTY; 1791 } 1792 1793 /** 1794 * Get the id of the dynamic content type matching to given ones 1795 * @param contentTypes The content types 1796 * @param mixinTypes The mixins 1797 * @return the id of dynamic content type or null if no dynamic content type was found 1798 */ 1799 public String getDynamicContentTypeId(String[] contentTypes, String[] mixinTypes) 1800 { 1801 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(contentTypes, mixinTypes); 1802 if (dynamicContentType != null) 1803 { 1804 return dynamicContentType.getId(); 1805 } 1806 1807 return null; 1808 } 1809 1810 /** 1811 * Get the plugin name of content type to use for rendering 1812 * 1813 * @param content The content 1814 * @return the plugin name of dynamic or standard content type 1815 */ 1816 public String getContentTypePluginForRendering(Content content) 1817 { 1818 String dynamicContentTypePlugin = getDynamicContentTypePlugin(content.getTypes(), content.getMixinTypes()); 1819 if (dynamicContentTypePlugin != null) 1820 { 1821 return dynamicContentTypePlugin; 1822 } 1823 1824 ContentType firstContentType = getFirstContentType(content); 1825 return firstContentType != null ? firstContentType.getPluginName() : StringUtils.EMPTY; 1826 } 1827 1828 /** 1829 * Get the plugin name of the dynamic content type matching to given ones 1830 * @param contentTypes The content types 1831 * @param mixinTypes The mixins 1832 * @return the plugin name of dynamic content type or null if no dynamic content type was found 1833 */ 1834 public String getDynamicContentTypePlugin(String[] contentTypes, String[] mixinTypes) 1835 { 1836 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(contentTypes, mixinTypes); 1837 if (dynamicContentType != null) 1838 { 1839 return dynamicContentType.getPluginName(); 1840 } 1841 1842 return null; 1843 } 1844 1845 /** 1846 * Retrieves the label of the content type. 1847 * 1848 * @param content The content 1849 * @return the label. 1850 */ 1851 public I18nizableText getContentTypeLabel(Content content) 1852 { 1853 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1854 if (dynamicContentType != null) 1855 { 1856 return dynamicContentType.getLabel(); 1857 } 1858 1859 ContentType firstContentType = getFirstContentType(content); 1860 return firstContentType != null ? firstContentType.getLabel() : new I18nizableText(StringUtils.EMPTY); 1861 } 1862 1863 /** 1864 * Retrieves the description of the content type. 1865 * 1866 * @param content The content 1867 * @return the label. 1868 */ 1869 public I18nizableText getContentTypeDescription(Content content) 1870 { 1871 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1872 if (dynamicContentType != null) 1873 { 1874 return dynamicContentType.getDescription(); 1875 } 1876 1877 ContentType firstContentType = getFirstContentType(content); 1878 return firstContentType != null ? firstContentType.getDescription() : new I18nizableText(StringUtils.EMPTY); 1879 } 1880 1881 /** 1882 * Retrieves the default title of the content type. 1883 * 1884 * @param content The content 1885 * @return the label. 1886 */ 1887 public I18nizableText getContentTypeDefaultTitle(Content content) 1888 { 1889 DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1890 if (dynamicContentType != null) 1891 { 1892 return dynamicContentType.getDefaultTitle(); 1893 } 1894 1895 ContentType firstContentType = getFirstContentType(content); 1896 return firstContentType != null ? firstContentType.getDefaultTitle() : new I18nizableText(StringUtils.EMPTY); 1897 } 1898 1899 /** 1900 * Retrieves the category of the content type. 1901 * 1902 * @param content The content 1903 * @return the label. 1904 */ 1905 public I18nizableText getContentTypeCategory(Content content) 1906 { 1907 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1908 if (dynamicCTDescriptor != null) 1909 { 1910 return dynamicCTDescriptor.getCategory(); 1911 } 1912 1913 ContentType firstContentType = getFirstContentType(content); 1914 return firstContentType != null ? firstContentType.getCategory() : new I18nizableText(StringUtils.EMPTY); 1915 } 1916 1917 /** 1918 * Retrieves the CSS class to use as glyph icon of the content 1919 * @param content The content 1920 * @return the glyph 1921 */ 1922 public String getIconGlyph(Content content) 1923 { 1924 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1925 if (dynamicCTDescriptor != null) 1926 { 1927 return dynamicCTDescriptor.getIconGlyph(); 1928 } 1929 1930 ContentType firstContentType = getFirstContentType(content); 1931 return firstContentType != null ? firstContentType.getIconGlyph() : null; 1932 } 1933 1934 /** 1935 * Retrieves the CSS class to use as decorator above the main icon 1936 * @param content The content 1937 * @return the decorator CSS class name 1938 */ 1939 public String getIconDecorator(Content content) 1940 { 1941 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1942 if (dynamicCTDescriptor != null) 1943 { 1944 return dynamicCTDescriptor.getIconDecorator(); 1945 } 1946 1947 ContentType firstContentType = getFirstContentType(content); 1948 return firstContentType != null ? firstContentType.getIconDecorator() : null; 1949 } 1950 1951 /** 1952 * Retrieves the URL of the icon without the context path. 1953 * 1954 * @param content The content 1955 * @return the icon URL for the small image 16x16. 1956 */ 1957 public String getSmallIcon(Content content) 1958 { 1959 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1960 if (dynamicCTDescriptor != null) 1961 { 1962 return dynamicCTDescriptor.getSmallIcon(); 1963 } 1964 1965 ContentType firstContentType = getFirstContentType(content); 1966 return firstContentType != null ? firstContentType.getSmallIcon() : StringUtils.EMPTY; 1967 } 1968 1969 /** 1970 * Retrieves the URL of the icon without the context path. 1971 * 1972 * @param content The content 1973 * @return the icon URL for the medium image 32x32. 1974 */ 1975 public String getMediumIcon(Content content) 1976 { 1977 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1978 if (dynamicCTDescriptor != null) 1979 { 1980 return dynamicCTDescriptor.getMediumIcon(); 1981 } 1982 1983 ContentType firstContentType = getFirstContentType(content); 1984 return firstContentType != null ? firstContentType.getMediumIcon() : StringUtils.EMPTY; 1985 } 1986 1987 /** 1988 * Retrieves the URL of the icon without the context path. 1989 * 1990 * @param content The content 1991 * @return the icon URL for the large image 48x48. 1992 */ 1993 public String getLargeIcon(Content content) 1994 { 1995 DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1996 if (dynamicCTDescriptor != null) 1997 { 1998 return dynamicCTDescriptor.getLargeIcon(); 1999 } 2000 2001 ContentType firstContentType = getFirstContentType(content); 2002 return firstContentType != null ? firstContentType.getLargeIcon() : StringUtils.EMPTY; 2003 } 2004 2005 /** 2006 * Get the content type which determines the content icons and rendering 2007 * 2008 * @param content The content 2009 * @return The main content type 2010 */ 2011 public ContentType getFirstContentType(Content content) 2012 { 2013 TreeSet<ContentType> treeSet = new TreeSet<>(new ContentTypeComparator()); 2014 2015 for (String id : content.getTypes()) 2016 { 2017 ContentType contentType = _getContentTypeEP().getExtension(id); 2018 if (contentType != null) 2019 { 2020 treeSet.add(contentType); 2021 } 2022 else 2023 { 2024 if (getLogger().isWarnEnabled()) 2025 { 2026 getLogger().warn(String.format("Trying to get an unknown content type : '%s'.", id)); 2027 } 2028 } 2029 } 2030 2031 return !treeSet.isEmpty() ? treeSet.first() : null; 2032 } 2033 2034 /** 2035 * Get the metadata definition for the "title" standard metadata. 2036 * @return The standard title metadata definition. 2037 * @deprecated Use {@link ContentTypesHelper#getTitleAttributeDefinition()} instead 2038 */ 2039 @Deprecated 2040 public static MetadataDefinition getTitleMetadataDefinition() 2041 { 2042 MetadataDefinition def = new MetadataDefinition(); 2043 def.setId("title"); 2044 def.setName("title"); 2045 def.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_LABEL")); 2046 def.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_DESCRIPTION")); 2047 def.setType(MetadataType.STRING); 2048 def.setMultiple(false); 2049 2050 return def; 2051 } 2052 2053 /** 2054 * Retrieves the definition for the standard "title" attribute. 2055 * @return the standard "title" attribute. 2056 */ 2057 public AttributeDefinition<String> getTitleAttributeDefinition() 2058 { 2059 AttributeDefinition<String> definition = new AttributeDefinition<>(); 2060 definition.setName(Content.ATTRIBUTE_TITLE); 2061 definition.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_LABEL")); 2062 definition.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_DESCRIPTION")); 2063 definition.setType(_contentAttributeTypeExtensionPoint.getExtension(ModelItemTypeConstants.STRING_TYPE_ID)); 2064 definition.setMultiple(false); 2065 return definition; 2066 } 2067 2068 /** 2069 * Determines if the content type is an archived content type 2070 * @param cTypeId The id of content type 2071 * @return true if the content type is an org.ametys.cms.ArchivedContent 2072 */ 2073 public boolean isArchivedContentType(String cTypeId) 2074 { 2075 return getAncestors(cTypeId).contains(ARCHIVED_CONTENT_TYPE); 2076 } 2077 2078 class ContentTypeComparator implements Comparator<ContentType> 2079 { 2080 @Override 2081 public int compare(ContentType c1, ContentType c2) 2082 { 2083 I18nizableText t1 = c1.getLabel(); 2084 I18nizableText t2 = c2.getLabel(); 2085 2086 String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); 2087 String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); 2088 2089 int compareTo = str1.toString().compareTo(str2.toString()); 2090 if (compareTo == 0) 2091 { 2092 // Content types have same keys but there are not equals, so do 2093 // not return 0 to add it in TreeSet 2094 // Indeed, in a TreeSet implementation two elements that are 2095 // equal by the method compareTo are, from the standpoint of the 2096 // set, equal 2097 return 1; 2098 } 2099 return compareTo; 2100 } 2101 } 2102 2103 @Deprecated 2104 private Cache<CacheKey, MetadataSet> _getCacheForView() 2105 { 2106 return _cacheManager.get(__VIEW_METADATASET); 2107 } 2108 2109 @Deprecated 2110 private Cache<CacheKey, MetadataSet> _getCacheForEdition() 2111 { 2112 return _cacheManager.get(__EDITION_METADATASET); 2113 } 2114 2115 private Cache<CacheKey, View> _getViewCache() 2116 { 2117 return _cacheManager.get(__VIEW_CACHE); 2118 } 2119 2120 static final class CacheKey extends AbstractCacheKey 2121 { 2122 private CacheKey(Set<String> contentTypeIds, Set<String> mixinIds, String viewName) 2123 { 2124 super(contentTypeIds, mixinIds, viewName); 2125 } 2126 2127 static CacheKey of(Set<String> contentTypeIds, Set<String> mixinIds, String viewName) 2128 { 2129 return new CacheKey(contentTypeIds, mixinIds, viewName); 2130 } 2131 } 2132 2133}