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