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()) || (!category.isI18n() && category.getLabel().isEmpty())) 205 { 206 category = new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENT_CREATECONTENTMENU_GROUP_10_CONTENT"); 207 } 208 categoriesSet.add(category); 209 } 210 for (I18nizableText category : categoriesSet) 211 { 212 @SuppressWarnings("unchecked") 213 Map<String, Object> enhancedCategory = (Map<String, Object>) _contentTypeInformationHelper.getEnhancedMultilingualString(category, false); 214 if (category.isI18n()) 215 { 216 enhancedCategory.put("key", category.getKey()); 217 enhancedCategory.put("catalogue", category.getCatalogue()); 218 } 219 enhancedCategory.put("isNew", false); 220 categories.add(enhancedCategory); 221 } 222 // Course of new categories 223 for (Map<String, Object> newCategory : _newCategories) 224 { 225 categories.add(newCategory); 226 } 227 Map<String, Object> newCategory = new HashMap<>(); 228 newCategory.put("isMultilingual", true); 229 I18nizableText newCategoryKey = new I18nizableText("plugin.contenttypes-editor", 230 "PLUGINS_CONTENTTYPESEDITOR_ADD_CONTENT_TYPE_DIALOG_CONTENT_TYPE_NEW_CATEGORY_INPUT_LABEL"); 231 Map<String, String> categoryTranslation = new HashMap<>(); 232 categoryTranslation.put("fr", _i18nUtils.translate(newCategoryKey, "fr")); 233 categoryTranslation.put("en", _i18nUtils.translate(newCategoryKey, "en")); 234 newCategory.put("values", categoryTranslation); 235 newCategory.put("isNew", true); 236 newCategory.put("key", newCategoryKey.getKey()); 237 newCategory.put("catalogue", newCategoryKey.getCatalogue()); 238 categories.add(newCategory); 239 return categories; 240 } 241 242 /** 243 * Retrieves attribute types 244 * @return Existing attribute types 245 */ 246 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 247 public Collection<Map<String, String>> getAttributeType() 248 { 249 Collection<Map<String, String>> attributeTypes = new HashSet<>(); 250 251 for (String typeId : _contentAttributeTypeExtensionPoint.getExtensionsIds()) 252 { 253 Map<String, String> type = new HashMap<>(); 254 255 type.put("id", typeId); 256 type.put("label", WordUtils.capitalize(typeId)); 257 258 attributeTypes.add(type); 259 } 260 261 return attributeTypes; 262 } 263 264 /** 265 * Get model items paths of the given content type 266 * @param contentTypeId the content type's identifier 267 * @return The model items paths of the given content type 268 */ 269 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 270 public Collection<Map<String, String>> getAttributePaths(String contentTypeId) 271 { 272 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 273 return _getModelItemsPaths(contentType.getModelItems()); 274 } 275 276 private Collection<Map<String, String>> _getModelItemsPaths(Collection<? extends ModelItem> modelItems) 277 { 278 Collection<Map<String, String>> modelItemPaths = new HashSet<>(); 279 280 for (ModelItem modelItem : modelItems) 281 { 282 Map<String, String> modelItemPath = new HashMap<>(); 283 modelItemPath.put("name", modelItem.getPath()); 284 modelItemPaths.add(modelItemPath); 285 286 if (modelItem instanceof ModelItemGroup) 287 { 288 modelItemPaths.addAll(_getModelItemsPaths(((ModelItemGroup) modelItem).getModelItems())); 289 } 290 } 291 292 return modelItemPaths; 293 } 294 295 /** 296 * Retrieves model item names of a content type 297 * @param contentTypeId The id of content type 298 * @return Model item names of content type 299 */ 300 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 301 public Collection<Map<String, Object>> getAttributeNames(String contentTypeId) 302 { 303 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 304 return _getModelItemsNames(contentType.getModelItems()); 305 } 306 307 private Collection<Map<String, Object>> _getModelItemsNames(Collection<? extends ModelItem> modelItems) 308 { 309 Collection<Map<String, Object>> modelItemNames = new HashSet<>(); 310 311 for (ModelItem modelItem : modelItems) 312 { 313 Map<String, Object> modelItemName = new HashMap<>(); 314 315 modelItemName.put("name", modelItem.getName()); 316 modelItemName.put("path", modelItem.getPath()); 317 modelItemName.put("isRepeaterOrComposite", modelItem instanceof RepeaterDefinition || modelItem instanceof CompositeDefinition); 318 319 modelItemNames.add(modelItemName); 320 321 if (modelItem instanceof ModelItemGroup) 322 { 323 modelItemNames.addAll(_getModelItemsNames(((ModelItemGroup) modelItem).getModelItems())); 324 } 325 } 326 327 return modelItemNames; 328 } 329 330 /** 331 * Add a new category to list of new category 332 * 333 * @param newCategory The new category 334 */ 335 public void addNewCategory(Map<String, Object> newCategory) 336 { 337 if (newCategory != null && newCategory.size() > 0) 338 { 339 _newCategories.add(newCategory); 340 } 341 } 342 343 private Map<String, Object> getView(String name, String iconGlyph) 344 { 345 Map<String, Object> view = new HashMap<>(); 346 view.put("dataType", ContentTypeAttributeDataType.METADATA_SET.name().toLowerCase()); 347 view.put("name", name); 348 Map<String, Object> label = new HashMap<>(); 349 label.put("isMultilingual", false); 350 label.put("values", name); 351 view.put("label", label); 352 view.put("isEdition", false); 353 view.put("iconGlyph", iconGlyph); 354 view.put("iconCls", iconGlyph); 355 view.put("leaf", true); 356 view.put("isInternal", false); 357 return view; 358 } 359 360 /** 361 * Check if a content type is editable 362 * 363 * @param contentTypeId Id of content type 364 * @return True if the content type is editable 365 */ 366 public boolean isEditableContentType(String contentTypeId) 367 { 368 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 369 return contentType instanceof AutomaticContentType; 370 } 371 372 /** 373 * Get all attributes which can be parent reference 374 * 375 * @param contentTypeId The content type id 376 * @param superTypesIds Supertype of content type 377 * @param recoverAttributeList Updated attributes of content type 378 * @return all attributes which can be parent reference 379 */ 380 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 381 public Collection<Map<String, String>> getParentReferenceAttributes(String contentTypeId, List<String> superTypesIds, Object recoverAttributeList) 382 { 383 Collection<Map<String, String>> parentReferenceModelItems = new HashSet<>(); 384 String stateContentType = _contentTypeStateComponent.getStateContentType(contentTypeId); 385 386 Set<String> newContentTypes = _contentTypeStateComponent.getContentTypeMarkedAsNew(); 387 388 if (newContentTypes.contains(contentTypeId) || stateContentType.equals(ContentTypeState.EDIT.name().toLowerCase())) 389 { 390 List attributeList = (List) recoverAttributeList; 391 for (Object recoverAttribute : attributeList) 392 { 393 parentReferenceModelItems.addAll(_getParentReferenceRecoverModelItems(recoverAttribute)); 394 } 395 } 396 else if (stateContentType.equals(ContentTypeState.RESTART.name().toLowerCase())) 397 { 398 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 399 parentReferenceModelItems.addAll(_getParentReferenceModelItems(contentType.getModelItems())); 400 } 401 402 if (superTypesIds != null && !superTypesIds.isEmpty()) 403 { 404 for (String newContentTypeId : newContentTypes) 405 { 406 ContentType newContentType = _contentTypeExtensionPoint.getExtension(newContentTypeId); 407 if (newContentType != null) 408 { 409 parentReferenceModelItems.addAll(_getParentReferenceModelItems(newContentType.getModelItems())); 410 } 411 } 412 } 413 414 return parentReferenceModelItems; 415 } 416 417 private Collection<Map<String, String>> _getParentReferenceRecoverModelItems(Object recoverModelItem) 418 { 419 Collection<Map<String, String>> parentReferenceModelItems = new HashSet<>(); 420 421 if (recoverModelItem instanceof Map) 422 { 423 Map modelItemInfo = (Map) recoverModelItem; 424 425 Object recoveredModelItemName = modelItemInfo.get("name"); 426 if (recoveredModelItemName != null && recoveredModelItemName instanceof String) 427 { 428 String modelItemName = (String) recoveredModelItemName; 429 if (StringUtils.isNotBlank(modelItemName)) 430 { 431 Object recoveredModelItemDataTypeId = modelItemInfo.get("type"); 432 if (recoveredModelItemDataTypeId != null && recoveredModelItemDataTypeId instanceof String) 433 { 434 String dataTypeId = (String) recoveredModelItemDataTypeId; 435 if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(dataTypeId)) 436 { 437 Object recoverInvertRelationPath = modelItemInfo.get("invertRelationPath"); 438 if (recoverInvertRelationPath != null) 439 { 440 String invertRelationPath = (String) recoverInvertRelationPath; 441 if (StringUtils.isNotBlank(invertRelationPath)) 442 { 443 Map<String, String> parentReferenceAttribute = new HashMap<>(); 444 parentReferenceAttribute.put("name", modelItemName); 445 parentReferenceModelItems.add(parentReferenceAttribute); 446 } 447 } 448 } 449 } 450 Object recoveredAttributeChildren = modelItemInfo.get("children"); 451 if (recoveredAttributeChildren != null && recoveredAttributeChildren instanceof List) 452 { 453 List attributeChildrens = (List) recoveredAttributeChildren; 454 for (Object recoverAttributeChildren : attributeChildrens) 455 { 456 parentReferenceModelItems.addAll(_getParentReferenceRecoverModelItems(recoverAttributeChildren)); 457 } 458 } 459 } 460 } 461 } 462 463 return parentReferenceModelItems; 464 } 465 466 private Collection<Map<String, String>> _getParentReferenceModelItems(Collection<? extends ModelItem> modelItems) 467 { 468 Collection<Map<String, String>> parentReferenceModelItems = new HashSet<>(); 469 470 for (ModelItem modelItem : modelItems) 471 { 472 if (modelItem instanceof ContentAttributeDefinition && StringUtils.isNotBlank(((ContentAttributeDefinition) modelItem).getInvertRelationPath())) 473 { 474 Map<String, String> data = new HashMap<>(); 475 data.put("name", modelItem.getName()); 476 parentReferenceModelItems.add(data); 477 } 478 479 if (modelItem instanceof ModelItemGroup) 480 { 481 parentReferenceModelItems.addAll(_getParentReferenceModelItems(((ModelItemGroup) modelItem).getModelItems())); 482 } 483 } 484 485 return parentReferenceModelItems; 486 } 487 488 /** 489 * Get invalid content if the attribute argument becomes mandatory 490 * 491 * @param contentTypeId The id of content type 492 * @param mandatoryAttributeName The name of the attribute 493 * @return a list with invalid content names 494 */ 495 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 496 public List<String> getInvalidContent(String contentTypeId, String mandatoryAttributeName) 497 { 498 List<String> invalidContents = new ArrayList<>(); 499 AmetysObjectIterable<AmetysObject> query = _ametysObjectResolver.query( 500 "//element(*,ametys:content)[@ametys-internal:contentType='" + contentTypeId + "' and not(@ametys:" + mandatoryAttributeName + ")]"); 501 Iterator<AmetysObject> it = query.iterator(); 502 while (it.hasNext()) 503 { 504 AmetysObject ametysObject = it.next(); 505 String contentName = ametysObject.getName(); 506 invalidContents.add(contentName); 507 } 508 return invalidContents; 509 } 510 511 /** 512 * Get all default widget 513 * 514 * @return All Default widget 515 */ 516 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 517 public Collection<Map<String, String>> getWidgets() 518 { 519 Collection<Map<String, String>> widgets = new HashSet<>(); 520 for (String id : _widgetsManager.getExtensionsIds()) 521 { 522 Map<String, String> widgetInfo = new HashMap<>(); 523 widgetInfo.put("id", id); 524 widgetInfo.put("label", id); 525 widgets.add(widgetInfo); 526 } 527 return widgets; 528 } 529 530 /** 531 * Get if a content type exists 532 * @param contentTypeId The id of content type 533 * @return True if the content type id exists 534 */ 535 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 536 public boolean isExistingContentType(String contentTypeId) 537 { 538 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 539 return contentType != null ? true : false; 540 } 541}