001/* 002 * Copyright 2018 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.plugins.contenttypeseditor.edition; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.Iterator; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Set; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030 031import org.apache.avalon.framework.component.Component; 032import org.apache.avalon.framework.configuration.ConfigurationException; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.commons.lang.StringUtils; 037import org.apache.commons.lang.WordUtils; 038 039import org.ametys.cms.contenttype.AutomaticContentType; 040import org.ametys.cms.contenttype.ContentType; 041import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 042import org.ametys.cms.contenttype.ContentTypesHelper; 043import org.ametys.cms.contenttype.MetadataDefinition; 044import org.ametys.cms.contenttype.MetadataType; 045import org.ametys.cms.contenttype.RepeaterDefinition; 046import org.ametys.core.right.RightsExtensionPoint; 047import org.ametys.core.ui.Callable; 048import org.ametys.core.ui.widgets.WidgetsManager; 049import org.ametys.core.util.I18nUtils; 050import org.ametys.plugins.contenttypeseditor.ContentTypeInformationsHelper; 051import org.ametys.plugins.contenttypeseditor.ContentTypeInformationsHelper.ContentTypeAttributeDataType; 052import org.ametys.plugins.contenttypeseditor.edition.ContentTypeStateComponent.ContentTypeState; 053import org.ametys.plugins.repository.AmetysObject; 054import org.ametys.plugins.repository.AmetysObjectIterable; 055import org.ametys.plugins.repository.AmetysObjectResolver; 056import org.ametys.runtime.i18n.I18nizableText; 057import org.ametys.runtime.plugin.PluginsManager; 058import org.ametys.runtime.plugin.component.AbstractLogEnabled; 059 060/** 061 * This component propose method for helping to create and edit a content type 062 */ 063public class EditContentTypeInformationHelper extends AbstractLogEnabled implements Component, Serviceable 064{ 065 /** The Avalon role name */ 066 public static final String ROLE = EditContentTypeInformationHelper.class.getName(); 067 068 /** The content type helper */ 069 protected ContentTypesHelper _contentTypesHelper; 070 071 /** The content type extension point instance */ 072 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 073 074 /** The content type extension point instance */ 075 protected ContentTypeInformationsHelper _contentTypeInformationHelper; 076 077 /** The content type state component instance */ 078 protected ContentTypeStateComponent _contentTypeStateComponent; 079 080 /** The right extension point instance */ 081 protected RightsExtensionPoint _rightsExtensionPoint; 082 083 /** The i18nUtils instance */ 084 protected I18nUtils _i18nUtils; 085 086 /** The ametys object resolver instance */ 087 protected AmetysObjectResolver _ametysObjectResolver; 088 089 /** The widgets manager */ 090 protected WidgetsManager _widgetsManager; 091 092 private Collection<Map<String, Object>> _newCategories; 093 094 @Override 095 public void service(ServiceManager manager) throws ServiceException 096 { 097 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 098 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 099 _contentTypeInformationHelper = (ContentTypeInformationsHelper) manager.lookup(ContentTypeInformationsHelper.ROLE); 100 _contentTypeStateComponent = (ContentTypeStateComponent) manager.lookup(ContentTypeStateComponent.ROLE); 101 _rightsExtensionPoint = (RightsExtensionPoint) manager.lookup(RightsExtensionPoint.ROLE); 102 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 103 _newCategories = new HashSet<>(); 104 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 105 _widgetsManager = (WidgetsManager) manager.lookup(WidgetsManager.ROLE); 106 } 107 108 /** 109 * Determine if content types are compatible 110 * 111 * @param contentTypeIds Ids of content types 112 * @return True if content types are compatible 113 */ 114 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 115 public boolean areContentTypesCompatible(List<String> contentTypeIds) 116 { 117 try 118 { 119 _contentTypesHelper.getMetadataDefinitions(contentTypeIds.toArray(new String[contentTypeIds.size()])); 120 return true; 121 } 122 catch (ConfigurationException e) 123 { 124 getLogger().error("Content types are not compatibles", e); 125 return false; 126 } 127 } 128 129 /** 130 * Get names of active plugins 131 * 132 * @return Names of active plugins 133 */ 134 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 135 public Collection<Map<String, String>> getPluginNames() 136 { 137 return PluginsManager.getInstance().getPluginNames().stream().map(name -> _toMap(name)).collect(Collectors.toList()); 138 } 139 140 private Map<String, String> _toMap(String name) 141 { 142 Map<String, String> map = new HashMap<>(); 143 map.put("id", name); 144 map.put("label", name); 145 return map; 146 } 147 148 /** 149 * Retrieve content type informations 150 * 151 * @param superTypesIds Ids of super content types 152 * @return Content type informations : metadatas of super content types and 153 * default metadataSets 154 */ 155 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 156 public Map<String, Object> getContentTypeInfos(List<Map<String, String>> superTypesIds) 157 { 158 Map<String, Object> contentTypeInfos = new HashMap<>(); 159 160 List<Map<String, Object>> metadata = new ArrayList<>(); 161 // Adding metadata of superType 162 if (superTypesIds != null && !superTypesIds.isEmpty()) 163 { 164 for (Map<String, String> superTypeId : superTypesIds) 165 { 166 ContentType cType = _contentTypeExtensionPoint.getExtension(superTypeId.get("id")); 167 metadata.addAll(_contentTypeInformationHelper.getMetadata(cType, true, false)); 168 } 169 contentTypeInfos.put("metadata", metadata); 170 } 171 // Adding default metadataSets 172 List<Map<String, Object>> metadataSets = new ArrayList<>(); 173 metadataSets.add(getMetadataSet("main", true, "ametysicon-document112")); 174 metadataSets.add(getMetadataSet("main", false, "ametysicon-document112")); 175 metadataSets.add(getMetadataSet("abstract", false, "ametysicon-document77")); 176 metadataSets.add(getMetadataSet("link", false, "ametysicon-internet58")); 177 metadataSets.add(getMetadataSet("details", false, "ametysicon-column3")); 178 metadataSets.add(getMetadataSet("index", false, "ametysicon-column3")); 179 contentTypeInfos.put("metadataSets", metadataSets); 180 181 return contentTypeInfos; 182 } 183 184 /** 185 * Get all categories and categories created with the content type editor 186 * 187 * @return All categories 188 */ 189 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 190 public Collection<Map<String, Object>> getCategories() 191 { 192 Collection<Map<String, Object>> categories = new LinkedHashSet<>(); 193 Set<I18nizableText> categoriesSet = new HashSet<>(); 194 Set<String> contentTypeIds = _contentTypeExtensionPoint.getExtensionsIds(); 195 for (String contentTypeId : contentTypeIds) 196 { 197 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 198 I18nizableText category = contentType.getCategory(); 199 if ((category.isI18n() && category.getKey().isEmpty()) || (!category.isI18n() && category.getLabel().isEmpty())) 200 { 201 category = new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENT_CREATECONTENTMENU_GROUP_10_CONTENT"); 202 } 203 categoriesSet.add(category); 204 } 205 for (I18nizableText category : categoriesSet) 206 { 207 @SuppressWarnings("unchecked") 208 Map<String, Object> enhancedCategory = (Map<String, Object>) _contentTypeInformationHelper.getEnhancedMultilingualString(category, false); 209 if (category.isI18n()) 210 { 211 enhancedCategory.put("key", category.getKey()); 212 enhancedCategory.put("catalogue", category.getCatalogue()); 213 } 214 enhancedCategory.put("isNew", false); 215 categories.add(enhancedCategory); 216 } 217 // Course of new categories 218 for (Map<String, Object> newCategory : _newCategories) 219 { 220 categories.add(newCategory); 221 } 222 Map<String, Object> newCategory = new HashMap<>(); 223 newCategory.put("isMultilingual", true); 224 I18nizableText newCategoryKey = new I18nizableText("plugin.contenttypes-editor", 225 "PLUGINS_CONTENTTYPESEDITOR_ADD_CONTENT_TYPE_DIALOG_CONTENT_TYPE_NEW_CATEGORY_INPUT_LABEL"); 226 Map<String, String> categoryTranslation = new HashMap<>(); 227 categoryTranslation.put("fr", _i18nUtils.translate(newCategoryKey, "fr")); 228 categoryTranslation.put("en", _i18nUtils.translate(newCategoryKey, "en")); 229 newCategory.put("values", categoryTranslation); 230 newCategory.put("isNew", true); 231 newCategory.put("key", newCategoryKey.getKey()); 232 newCategory.put("catalogue", newCategoryKey.getCatalogue()); 233 categories.add(newCategory); 234 return categories; 235 } 236 237 /** 238 * Retrieve metadata type 239 * 240 * @return Existing metadata type 241 */ 242 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 243 public Collection<Map<String, String>> getMetadataType() 244 { 245 Collection<Map<String, String>> metadataType = new HashSet<>(); 246 247 Set<String> metadataTypeAsSet = Stream.of(MetadataType.values()).map(Enum::name).collect(Collectors.toSet()); 248 for (String metadata : metadataTypeAsSet) 249 { 250 Map<String, String> meta = new HashMap<>(); 251 meta.put("id", metadata.toLowerCase()); 252 meta.put("label", WordUtils.capitalize(metadata.toLowerCase())); 253 metadataType.add(meta); 254 } 255 Map<String, String> meta = new HashMap<>(); 256 meta.put("id", "repeater"); 257 meta.put("label", "Repeater"); 258 metadataType.add(meta); 259 return metadataType; 260 } 261 262 /** 263 * Get metadata paths of a content type 264 * 265 * @param contentTypeId The id of content type 266 * @return Metadata paths of content type 267 */ 268 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 269 public Collection<Map<String, String>> getMetadataPaths(String contentTypeId) 270 { 271 Collection<Map<String, String>> metadataPaths = new HashSet<>(); 272 273 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 274 275 for (String rootMetadataName : contentType.getMetadataNames()) 276 { 277 MetadataDefinition definition = contentType.getMetadataDefinition(rootMetadataName); 278 _getChildrenMetadataPaths(definition, definition.getName(), metadataPaths); 279 } 280 281 return metadataPaths; 282 } 283 284 private void _getChildrenMetadataPaths(MetadataDefinition definition, String parentPath, Collection<Map<String, String>> metadataPaths) 285 { 286 Map<String, String> metadataPath = new HashMap<>(); 287 metadataPath.put("name", parentPath); 288 metadataPaths.add(metadataPath); 289 for (String childrenMetadataPath : definition.getMetadataNames()) 290 { 291 String path = parentPath + "/" + childrenMetadataPath.toLowerCase(); 292 metadataPath = new HashMap<>(); 293 metadataPath.put("name", path); 294 metadataPaths.add(metadataPath); 295 MetadataDefinition childrenDefinition = definition.getMetadataDefinition(childrenMetadataPath); 296 _getChildrenMetadataPaths(childrenDefinition, parentPath, metadataPaths); 297 } 298 } 299 300 /** 301 * Get metadata names of a content type 302 * 303 * @param contentTypeId The id of content type 304 * @return Metadata names of content type 305 */ 306 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 307 public Collection<Map<String, Object>> getMetadataNames(String contentTypeId) 308 { 309 Collection<Map<String, Object>> metadataNames = new HashSet<>(); 310 311 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 312 313 for (String rootMetadataName : contentType.getMetadataNames()) 314 { 315 MetadataDefinition definition = contentType.getMetadataDefinition(rootMetadataName); 316 _getChildrenMetadataNames(definition, definition.getName(), metadataNames); 317 } 318 319 return metadataNames; 320 } 321 322 private void _getChildrenMetadataNames(MetadataDefinition definition, String path, Collection<Map<String, Object>> metadataNames) 323 { 324 Map<String, Object> metadataName = new HashMap<>(); 325 metadataName.put("name", definition.getName()); 326 metadataName.put("path", path); 327 metadataName.put("id", definition.getName()); 328 if (definition instanceof RepeaterDefinition || definition.getType() == MetadataType.COMPOSITE) 329 { 330 metadataName.put("isRepeaterOrComposite", true); 331 } 332 metadataNames.add(metadataName); 333 for (String childrenMetadataName : definition.getMetadataNames()) 334 { 335 metadataName = new HashMap<>(); 336 metadataName.put("name", childrenMetadataName); 337 metadataName.put("id", childrenMetadataName); 338 metadataName.put("path", path + "/" + childrenMetadataName.toLowerCase()); 339 if (definition instanceof RepeaterDefinition || definition.getType() == MetadataType.COMPOSITE) 340 { 341 metadataName.put("isRepeaterOrComposite", true); 342 } 343 metadataNames.add(metadataName); 344 MetadataDefinition childrenDefinition = definition.getMetadataDefinition(childrenMetadataName); 345 _getChildrenMetadataNames(childrenDefinition, path, metadataNames); 346 } 347 } 348 349 /** 350 * Add a new category to list of new category 351 * 352 * @param newCategory The new category 353 */ 354 public void addNewCategory(Map<String, Object> newCategory) 355 { 356 if (newCategory != null && newCategory.size() > 0) 357 { 358 _newCategories.add(newCategory); 359 } 360 } 361 362 private Map<String, Object> getMetadataSet(String name, boolean isEdition, String iconGlyph) 363 { 364 Map<String, Object> metadataSet = new HashMap<>(); 365 metadataSet.put("dataType", ContentTypeAttributeDataType.METADATA_SET.name().toLowerCase()); 366 metadataSet.put("name", name); 367 Map<String, Object> label = new HashMap<>(); 368 label.put("isMultilingual", false); 369 label.put("values", name); 370 metadataSet.put("label", label); 371 metadataSet.put("isEdition", isEdition); 372 metadataSet.put("iconGlyph", iconGlyph); 373 metadataSet.put("iconCls", iconGlyph); 374 metadataSet.put("leaf", true); 375 return metadataSet; 376 } 377 378 /** 379 * Check if a content type is editable 380 * 381 * @param contentTypeId Id of content type 382 * @return True if the content type is editable 383 */ 384 public boolean isEditableContentType(String contentTypeId) 385 { 386 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 387 return contentType instanceof AutomaticContentType; 388 } 389 390 /** 391 * Get all metadata which can be parent reference 392 * 393 * @param contentTypeId The content type id 394 * @param superTypesIds Supertype of content type 395 * @param recoverMetadataList Updated metadata of content type 396 * @return all metadata which can be parent reference 397 */ 398 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 399 public Collection<Map<String, String>> getParentReferenceMetadatas(String contentTypeId, List<String> superTypesIds, Object recoverMetadataList) 400 { 401 Collection<Map<String, String>> parentReferenceMetadatas = new HashSet<>(); 402 String stateContentType = _contentTypeStateComponent.getStateContentType(contentTypeId); 403 404 Set<String> newContentTypes = _contentTypeStateComponent.getContentTypeMarkedAsNew(); 405 406 if (newContentTypes.contains(contentTypeId) || stateContentType.equals(ContentTypeState.EDIT.name().toLowerCase())) 407 { 408 List metadataList = (List) recoverMetadataList; 409 for (Object recoverMetadata : metadataList) 410 { 411 _getParentReferenceRecoverMetadata(recoverMetadata, parentReferenceMetadatas); 412 } 413 } 414 else if (stateContentType.equals(ContentTypeState.RESTART.name().toLowerCase())) 415 { 416 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 417 _getParentReferenceMetadatas(contentType, parentReferenceMetadatas); 418 } 419 420 if (superTypesIds != null && !superTypesIds.isEmpty()) 421 { 422 for (String newContentTypeId : newContentTypes) 423 { 424 ContentType newContentType = _contentTypeExtensionPoint.getExtension(newContentTypeId); 425 if (newContentType != null) 426 { 427 _getParentReferenceMetadatas(newContentType, parentReferenceMetadatas); 428 } 429 } 430 } 431 432 return parentReferenceMetadatas; 433 } 434 435 private void _getParentReferenceRecoverMetadata(Object recoverMetadata, Collection<Map<String, String>> parentReferenceMetadatas) 436 { 437 if (recoverMetadata instanceof Map) 438 { 439 Map metadataInfo = (Map) recoverMetadata; 440 441 Object recoveredMetadataName = metadataInfo.get("name"); 442 if (recoveredMetadataName != null && recoveredMetadataName instanceof String) 443 { 444 String metadataName = (String) recoveredMetadataName; 445 if (StringUtils.isNotBlank(metadataName)) 446 { 447 Object recoveredMetadataDataType = metadataInfo.get("type"); 448 if (recoveredMetadataDataType != null && recoveredMetadataDataType instanceof String) 449 { 450 String dataType = (String) recoveredMetadataDataType; 451 if (dataType.equals(MetadataType.CONTENT.name().toLowerCase())) 452 { 453 Object recoverInvertRelationPath = metadataInfo.get("invertRelationPath"); 454 if (recoverInvertRelationPath != null) 455 { 456 String invertRelationPath = (String) recoverInvertRelationPath; 457 if (StringUtils.isNotBlank(invertRelationPath)) 458 { 459 Map<String, String> parentReferenceMetadata = new HashMap<>(); 460 parentReferenceMetadata.put("name", metadataName); 461 parentReferenceMetadatas.add(parentReferenceMetadata); 462 } 463 } 464 } 465 } 466 Object recoveredMetadataChildren = metadataInfo.get("children"); 467 if (recoveredMetadataChildren != null && recoveredMetadataChildren instanceof List) 468 { 469 List metadataChildrens = (List) recoveredMetadataChildren; 470 for (Object recoverMetadataChildren : metadataChildrens) 471 { 472 _getParentReferenceRecoverMetadata(recoverMetadataChildren, parentReferenceMetadatas); 473 } 474 } 475 } 476 } 477 } 478 } 479 480 private void _getParentReferenceMetadatas(ContentType contentType, Collection<Map<String, String>> parentReferenceMetadatas) 481 { 482 Set<String> metadataNames = contentType.getMetadataNames(); 483 for (String metadataName : metadataNames) 484 { 485 MetadataDefinition metadataDefinition = contentType.getMetadataDefinition(metadataName); 486 if (metadataDefinition.getType() == MetadataType.CONTENT && StringUtils.isNotBlank(metadataDefinition.getInvertRelationPath())) 487 { 488 Map<String, String> data = new HashMap<>(); 489 data.put("name", metadataDefinition.getName()); 490 parentReferenceMetadatas.add(data); 491 } 492 _getChildrenParentReferenceMetadatas(metadataDefinition, parentReferenceMetadatas); 493 } 494 } 495 496 private void _getChildrenParentReferenceMetadatas(MetadataDefinition definition, Collection<Map<String, String>> parentReferenceMetadatas) 497 { 498 Set<String> metadataNames = definition.getMetadataNames(); 499 for (String metadataName : metadataNames) 500 { 501 MetadataDefinition metadataDefinition = definition.getMetadataDefinition(metadataName); 502 if (metadataDefinition.getType() == MetadataType.CONTENT && StringUtils.isNotBlank(metadataDefinition.getInvertRelationPath())) 503 { 504 Map<String, String> data = new HashMap<>(); 505 data.put("name", metadataDefinition.getName()); 506 parentReferenceMetadatas.add(data); 507 } 508 _getChildrenParentReferenceMetadatas(metadataDefinition, parentReferenceMetadatas); 509 } 510 } 511 512 /** 513 * Get invalid content if the metadata argument becomes mandatory 514 * 515 * @param contentTypeId The id of content type 516 * @param mandatoryMetadataName The name of the metadata 517 * @return a list with invalid content names 518 */ 519 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 520 public List<String> getInvalidContent(String contentTypeId, String mandatoryMetadataName) 521 { 522 List<String> invalidContents = new ArrayList<>(); 523 AmetysObjectIterable<AmetysObject> query = _ametysObjectResolver.query( 524 "//element(*,ametys:content)[@ametys-internal:contentType='" + contentTypeId + "' and not(@ametys:" + mandatoryMetadataName + ")]"); 525 Iterator<AmetysObject> it = query.iterator(); 526 while (it.hasNext()) 527 { 528 AmetysObject ametysObject = it.next(); 529 String contentName = ametysObject.getName(); 530 invalidContents.add(contentName); 531 } 532 return invalidContents; 533 } 534 535 /** 536 * Get all default widget 537 * 538 * @return All Default widget 539 */ 540 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 541 public Collection<Map<String, String>> getWidgets() 542 { 543 Collection<Map<String, String>> widgets = new HashSet<>(); 544 Map<String, Map<String, Map<String, String>>> defaultWidgets = _widgetsManager.getDefaultWidgets(); 545 546 for (Entry<String, Map<String, Map<String, String>>> widgetCategory : defaultWidgets.entrySet()) 547 { 548 for (Entry<String, Map<String, String>> widget : widgetCategory.getValue().entrySet()) 549 { 550 for (Entry<String, String> w : widget.getValue().entrySet()) 551 { 552 Map<String, String> widgetInfo = new HashMap<>(); 553 widgetInfo.put("id", w.getValue()); 554 widgetInfo.put("label", w.getValue()); 555 widgets.add(widgetInfo); 556 } 557 } 558 } 559 return widgets; 560 } 561 562 /** 563 * Get if a content type exists 564 * @param contentTypeId The id of content type 565 * @return True if the content type id exists 566 */ 567 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 568 public boolean isExistingContentType(String contentTypeId) 569 { 570 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 571 return contentType != null ? true : false; 572 } 573}