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.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.commons.lang.StringUtils; 034import org.apache.commons.lang.WordUtils; 035 036import org.ametys.cms.contenttype.AutomaticContentType; 037import org.ametys.cms.contenttype.ContentAttributeDefinition; 038import org.ametys.cms.contenttype.ContentType; 039import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 040import org.ametys.cms.contenttype.ContentTypesHelper; 041import org.ametys.cms.data.type.ModelItemTypeConstants; 042import org.ametys.cms.data.type.ModelItemTypeExtensionPoint; 043import org.ametys.core.right.RightsExtensionPoint; 044import org.ametys.core.ui.Callable; 045import org.ametys.core.ui.widgets.WidgetsManager; 046import org.ametys.core.util.I18nUtils; 047import org.ametys.plugins.contenttypeseditor.ContentTypeInformationsHelper; 048import org.ametys.plugins.contenttypeseditor.ContentTypeInformationsHelper.ContentTypeAttributeDataType; 049import org.ametys.plugins.contenttypeseditor.edition.ContentTypeStateComponent.ContentTypeState; 050import org.ametys.plugins.repository.AmetysObject; 051import org.ametys.plugins.repository.AmetysObjectIterable; 052import org.ametys.plugins.repository.AmetysObjectResolver; 053import org.ametys.plugins.repository.model.CompositeDefinition; 054import org.ametys.plugins.repository.model.RepeaterDefinition; 055import org.ametys.runtime.i18n.I18nizableText; 056import org.ametys.runtime.model.ModelItem; 057import org.ametys.runtime.model.ModelItemGroup; 058import org.ametys.runtime.plugin.PluginsManager; 059import org.ametys.runtime.plugin.component.AbstractLogEnabled; 060 061/** 062 * This component propose method for helping to create and edit a content type 063 */ 064public class EditContentTypeInformationHelper extends AbstractLogEnabled implements Component, Serviceable 065{ 066 /** The Avalon role name */ 067 public static final String ROLE = EditContentTypeInformationHelper.class.getName(); 068 069 /** The content type helper */ 070 protected ContentTypesHelper _contentTypesHelper; 071 072 /** The content type extension point instance */ 073 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 074 075 /** The extension point for available attribute types */ 076 protected ModelItemTypeExtensionPoint _contentAttributeTypeExtensionPoint; 077 078 /** The content type extension point instance */ 079 protected ContentTypeInformationsHelper _contentTypeInformationHelper; 080 081 /** The content type state component instance */ 082 protected ContentTypeStateComponent _contentTypeStateComponent; 083 084 /** The right extension point instance */ 085 protected RightsExtensionPoint _rightsExtensionPoint; 086 087 /** The i18nUtils instance */ 088 protected I18nUtils _i18nUtils; 089 090 /** The ametys object resolver instance */ 091 protected AmetysObjectResolver _ametysObjectResolver; 092 093 /** The widgets manager */ 094 protected WidgetsManager _widgetsManager; 095 096 private Collection<Map<String, Object>> _newCategories; 097 098 @Override 099 public void service(ServiceManager manager) throws ServiceException 100 { 101 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 102 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 103 _contentAttributeTypeExtensionPoint = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_CONTENT_ATTRIBUTE); 104 _contentTypeInformationHelper = (ContentTypeInformationsHelper) manager.lookup(ContentTypeInformationsHelper.ROLE); 105 _contentTypeStateComponent = (ContentTypeStateComponent) manager.lookup(ContentTypeStateComponent.ROLE); 106 _rightsExtensionPoint = (RightsExtensionPoint) manager.lookup(RightsExtensionPoint.ROLE); 107 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 108 _newCategories = new HashSet<>(); 109 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 110 _widgetsManager = (WidgetsManager) manager.lookup(WidgetsManager.ROLE); 111 } 112 113 /** 114 * Determine if content types are compatible 115 * 116 * @param contentTypeIds Ids of content types 117 * @return True if content types are compatible 118 */ 119 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 120 public boolean areContentTypesCompatible(List<String> contentTypeIds) 121 { 122 try 123 { 124 _contentTypesHelper.getModelItems(contentTypeIds.toArray(new String[contentTypeIds.size()])); 125 return true; 126 } 127 catch (IllegalArgumentException e) 128 { 129 getLogger().error("Content types are not compatibles", e); 130 return false; 131 } 132 } 133 134 /** 135 * Get names of active plugins 136 * 137 * @return Names of active plugins 138 */ 139 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 140 public Collection<Map<String, String>> getPluginNames() 141 { 142 return PluginsManager.getInstance().getPluginNames().stream().map(name -> _toMap(name)).collect(Collectors.toList()); 143 } 144 145 private Map<String, String> _toMap(String name) 146 { 147 Map<String, String> map = new HashMap<>(); 148 map.put("id", name); 149 map.put("label", name); 150 return map; 151 } 152 153 /** 154 * Retrieve content type informations 155 * 156 * @param superTypesIds Ids of super content types 157 * @return Content type informations : attributes of super content types and 158 * default views 159 */ 160 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 161 public Map<String, Object> getContentTypeInfos(List<Map<String, String>> superTypesIds) 162 { 163 Map<String, Object> contentTypeInfos = new HashMap<>(); 164 165 List<Map<String, Object>> attributes = new ArrayList<>(); 166 // Adding attributes of superType 167 if (superTypesIds != null && !superTypesIds.isEmpty()) 168 { 169 for (Map<String, String> superTypeId : superTypesIds) 170 { 171 ContentType cType = _contentTypeExtensionPoint.getExtension(superTypeId.get("id")); 172 attributes.addAll(_contentTypeInformationHelper.getModelItemsInformation(cType, true, false)); 173 } 174 contentTypeInfos.put("attributes", attributes); 175 } 176 // Adding default views 177 List<Map<String, Object>> views = new ArrayList<>(); 178 views.add(_getView("main", "ametysicon-document112")); 179 views.add(_getView("abstract", "ametysicon-document77")); 180 views.add(_getView("link", "ametysicon-internet58")); 181 views.add(_getView("details", "ametysicon-column3")); 182 views.add(_getView("index", "ametysicon-column3")); 183 contentTypeInfos.put("views", views); 184 185 return contentTypeInfos; 186 } 187 188 /** 189 * Get all categories and categories created with the content type editor 190 * 191 * @return All categories 192 */ 193 @Callable(right = "CMS_Rights_EditContentType", context = "/cms") 194 public Collection<Map<String, Object>> getCategories() 195 { 196 Collection<Map<String, Object>> categories = new LinkedHashSet<>(); 197 Set<I18nizableText> categoriesSet = new HashSet<>(); 198 Set<String> contentTypeIds = _contentTypeExtensionPoint.getExtensionsIds(); 199 for (String contentTypeId : contentTypeIds) 200 { 201 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 202 I18nizableText category = contentType.getCategory(); 203 if (category.isI18n() && category.getKey().isEmpty() 204 || !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.VIEW.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; 540 } 541}