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.clientsideelement.relations; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.LinkedHashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Objects; 027import java.util.Set; 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.commons.collections.CollectionUtils; 033import org.apache.commons.lang.ArrayUtils; 034import org.apache.commons.lang3.StringUtils; 035 036import org.ametys.cms.content.ContentHelper; 037import org.ametys.cms.contenttype.ContentConstants; 038import org.ametys.cms.contenttype.ContentType; 039import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 040import org.ametys.cms.contenttype.ContentTypesHelper; 041import org.ametys.cms.contenttype.MetadataDefinition; 042import org.ametys.cms.contenttype.MetadataType; 043import org.ametys.cms.contenttype.RepeaterDefinition; 044import org.ametys.cms.form.AbstractField.MODE; 045import org.ametys.cms.repository.Content; 046import org.ametys.cms.repository.WorkflowAwareContent; 047import org.ametys.cms.workflow.AllErrors; 048import org.ametys.cms.workflow.ContentWorkflowHelper; 049import org.ametys.cms.workflow.EditContentFunction; 050import org.ametys.cms.workflow.InvalidInputWorkflowException; 051import org.ametys.core.ui.Callable; 052import org.ametys.core.ui.StaticClientSideRelation; 053import org.ametys.plugins.repository.AmetysObjectResolver; 054import org.ametys.plugins.workflow.AbstractWorkflowComponent; 055import org.ametys.runtime.i18n.I18nizableText; 056import org.ametys.runtime.parameter.Errors; 057 058/** 059 * Set the metadata of type 'content' of a content, with another content 060 */ 061public class SetContentMetadataClientSideElement extends StaticClientSideRelation implements Component 062{ 063 /** The Ametys object resolver */ 064 protected AmetysObjectResolver _resolver; 065 /** The content type helper */ 066 protected ContentTypesHelper _ctypesHelper; 067 /** The content types extension point */ 068 protected ContentTypeExtensionPoint _ctypesEP; 069 /** The content workflow helper */ 070 protected ContentWorkflowHelper _contentWorkflowHelper; 071 /** The EP to filter meta */ 072 protected FilterCompatibleContentMetadataExtensionPoint _filterCompatibleContentMetadataExtensionPoint; 073 /** The content helper */ 074 protected ContentHelper _contentHelper; 075 076 @Override 077 public void service(ServiceManager manager) throws ServiceException 078 { 079 super.service(manager); 080 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 081 _ctypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 082 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 083 _ctypesEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 084 _contentWorkflowHelper = (ContentWorkflowHelper) manager.lookup(ContentWorkflowHelper.ROLE); 085 _filterCompatibleContentMetadataExtensionPoint = (FilterCompatibleContentMetadataExtensionPoint) manager.lookup(FilterCompatibleContentMetadataExtensionPoint.ROLE); 086 } 087 088 /** 089 * Find a metadata of type content in definition of 'contentIdsToReference' and set the metadata of contents 'contentIdsToEdit' with value 'contentIdsToReference' 090 * @param contentIdsToReference The list of content ids that will be added as values in the content field 091 * @param contentIdsToEdit The list of content ids to edit and that will have a metadata of type content modified 092 * @return A map with key success: true or false. if false, it can be due to errors (list of error messages) or because metadata cannot be determined then 'metadata' is a list of possible metadata 093 */ 094 @Callable 095 public Map<String, Object> getCompatibleMetadata(List<String> contentIdsToReference, List<String> contentIdsToEdit) 096 { 097 @SuppressWarnings("unchecked") 098 List<Content> contentToReference = (List<Content>) _resolve(contentIdsToReference); 099 @SuppressWarnings("unchecked") 100 List<Content> contentToEdit = (List<Content>) _resolve(contentIdsToEdit); 101 102 Set<MetadataDefinition> metadatadefs = _findCompatibleMetadata(contentToReference, contentToEdit); 103 104 Map<String, Object> returnValues = new HashMap<>(); 105 returnValues.put("success", false); 106 returnValues.put("metadata", _convert(metadatadefs)); 107 return returnValues; 108 } 109 110 /** 111 * Convert metadata definitions to JSON object 112 * @param metadatadefs The metadata definitions 113 * @return the JSON object 114 */ 115 protected List<Map<String, Object>> _convert(Set<MetadataDefinition> metadatadefs) 116 { 117 List<Map<String, Object>> metadatapaths = new ArrayList<>(); 118 119 for (MetadataDefinition metadataDef : metadatadefs) 120 { 121 metadatapaths.add(_convert(metadataDef)); 122 } 123 124 return metadatapaths; 125 } 126 127 /** 128 * Convert a metadata definition to JSON 129 * @param metadatadef the metadata definition 130 * @return the JSON object 131 */ 132 protected Map<String, Object> _convert(MetadataDefinition metadatadef) 133 { 134 String metaPath = metadatadef.getId(); 135 136 Map<String, Object> def = new HashMap<>(); 137 def.put("id", metaPath); 138 def.put("name", metadatadef.getName()); 139 def.put("label", metadatadef.getLabel()); 140 def.put("description", metadatadef.getDescription()); 141 142 if (metaPath.contains(ContentConstants.METADATA_PATH_SEPARATOR)) 143 { 144 String parentPath = StringUtils.substringBeforeLast(metaPath, ContentConstants.METADATA_PATH_SEPARATOR); 145 146 String cTypeId = metadatadef.getReferenceContentType(); 147 ContentType cType = _ctypesEP.getExtension(cTypeId); 148 149 MetadataDefinition parentMetadatadef = cType.getMetadataDefinitionByPath(parentPath); 150 def.put("parent", _convert(parentMetadatadef)); 151 } 152 153 return def; 154 } 155 156 /** 157 * Find the list of compatible metadata definition 158 * @param contentToReference the contents to reference 159 * @param contentToEdit the contents to edit 160 * @return the list of compatible metadata definition 161 */ 162 protected Set<MetadataDefinition> _findCompatibleMetadata(List<Content> contentToReference, List<Content> contentToEdit) 163 { 164 // First we need to find the type of the target metadata we are looking for 165 Collection<String> contentTypesToReference = new HashSet<>(); 166 for (Content content: contentToReference) 167 { 168 Set<String> ancestorsAndMySelf = new HashSet<>(); 169 170 String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 171 for (String id : allContentTypes) 172 { 173 ancestorsAndMySelf.addAll(_ctypesHelper.getAncestors(id)); 174 ancestorsAndMySelf.add(id); 175 } 176 177 if (contentTypesToReference.isEmpty()) 178 { 179 contentTypesToReference = ancestorsAndMySelf; 180 } 181 else 182 { 183 contentTypesToReference = CollectionUtils.intersection(contentTypesToReference, ancestorsAndMySelf); 184 } 185 } 186 187 // Second we need to know if this metadata will be mulitple or not 188 boolean requiresMultiple = contentToReference.size() > 1; 189 190 // Third we need to know the target content type 191 Collection<String> contentTypesToEdit = new ArrayList<>(); 192 for (Content content: contentToEdit) 193 { 194 Set<String> ancestorsAndMySelf = new HashSet<>(); 195 196 String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 197 for (String id : allContentTypes) 198 { 199 ancestorsAndMySelf.addAll(_ctypesHelper.getAncestors(id)); 200 ancestorsAndMySelf.add(id); 201 } 202 203 if (contentTypesToEdit.isEmpty()) 204 { 205 contentTypesToEdit = ancestorsAndMySelf; 206 } 207 else 208 { 209 contentTypesToEdit = CollectionUtils.intersection(contentTypesToEdit, ancestorsAndMySelf); 210 } 211 } 212 213 // Now lets navigate in the target content type to find a content metadata limited to the metadata content type (or its parent types), that is multiple if necessary 214 Set<MetadataDefinition> metadata = new HashSet<>(); 215 216 for (String targetContentTypeName : contentTypesToEdit) 217 { 218 ContentType targetContentType = _ctypesEP.getExtension(targetContentTypeName); 219 for (String metadataName : targetContentType.getMetadataNames()) 220 { 221 MetadataDefinition metaDef = targetContentType.getMetadataDefinition(metadataName); 222 metadata.addAll(_findCompatibleMetadata(contentToReference, contentToEdit, targetContentTypeName, metaDef, false, contentTypesToReference, requiresMultiple)); 223 } 224 } 225 226 return metadata; 227 } 228 229 private Set<MetadataDefinition> _findCompatibleMetadata(List<Content> contentToReference, List<Content> contentToEdit, String targetContentTypeName, MetadataDefinition metaDef, boolean anyParentIsMultiple, Collection<String> compatibleContentTypes, boolean requiresMultiple) 230 { 231 Set<MetadataDefinition> metadata = new HashSet<>(); 232 233 if (MetadataType.CONTENT.equals(metaDef.getType()) 234 && (metaDef.getContentType() == null || compatibleContentTypes.contains(metaDef.getContentType())) 235 && (!requiresMultiple || metaDef.isMultiple() || anyParentIsMultiple) 236 && metaDef.getReferenceContentType().equals(targetContentTypeName) 237 && _filterCompatibleContentMetadataExtensionPoint.filter(targetContentTypeName, metaDef, contentToReference, compatibleContentTypes) 238 && _hasRight(contentToEdit, metaDef)) 239 { 240 metadata.add(metaDef); 241 } 242 else if (MetadataType.COMPOSITE.equals(metaDef.getType())) 243 { 244 for (String subMetadataName : metaDef.getMetadataNames()) 245 { 246 MetadataDefinition subMetaDef = metaDef.getMetadataDefinition(subMetadataName); 247 metadata.addAll(_findCompatibleMetadata(contentToReference, contentToEdit, targetContentTypeName, subMetaDef, anyParentIsMultiple || metaDef instanceof RepeaterDefinition, compatibleContentTypes, requiresMultiple)); 248 } 249 } 250 251 return metadata; 252 } 253 254 private boolean _hasRight (List<Content> contentToEdit, MetadataDefinition metaDef) 255 { 256 for (Content content : contentToEdit) 257 { 258 if (!_ctypesHelper.canWrite(content, metaDef)) 259 { 260 return false; 261 } 262 } 263 return true; 264 } 265 266 /** 267 * Set the metadata 'metadatapath' of contents 'contentIdsToEdit' with value 'contentIdsToReference' 268 * @param contentIdsToReference The list of content ids that will be added as values in the content field 269 * @param contentIdsToEdit The map {key: content ids to edit and that will have a metadata of type content modified; value: the new position if metadata is multiple and it is a reorder of values. May be null or equals to -1 if it is not a reorder} 270 * @param contentsToEditToRemove The list of content to edit to remove currently referenced content. Keys are "contentId" and "valueToRemove" 271 * @param metadatapath The metadata path selected to do modification in the contentIdsToEdit contents 272 * @param workflowActionIds The ids of workflow actions to use to edit the metadata. Actions will be tested in this order and first available action will be used 273 * @return A map with key success: true or false. if false, it can be due to errors (list of error messages) 274 */ 275 @SuppressWarnings("unchecked") 276 @Callable 277 public Map<String, Object> setContentMetatada(List<String> contentIdsToReference, Map<String, Integer> contentIdsToEdit, List<Map<String, String>> contentsToEditToRemove, String metadatapath, List<String> workflowActionIds) 278 { 279 Map<WorkflowAwareContent, Integer> contentToEdit = (Map<WorkflowAwareContent, Integer>) _resolve(contentIdsToEdit); 280 281 List<String> errorIds = new ArrayList<>(); 282 List<I18nizableText> errorMessages = new ArrayList<>(); 283 284 _clean(contentsToEditToRemove, workflowActionIds, errorMessages, errorIds); 285 286 if (contentIdsToEdit.isEmpty() || contentIdsToReference.isEmpty()) 287 { 288 return _returnValue(errorMessages, errorIds); 289 } 290 291 Collection<String> contentTypesToEdit = new ArrayList<>(); 292 for (Content content: contentToEdit.keySet()) 293 { 294 Set<String> ancestorsAndMySelf = new HashSet<>(); 295 296 String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 297 for (String id : allContentTypes) 298 { 299 ancestorsAndMySelf.addAll(_ctypesHelper.getAncestors(id)); 300 ancestorsAndMySelf.add(id); 301 } 302 303 if (contentTypesToEdit.isEmpty()) 304 { 305 contentTypesToEdit = ancestorsAndMySelf; 306 } 307 else 308 { 309 contentTypesToEdit = CollectionUtils.intersection(contentTypesToEdit, ancestorsAndMySelf); 310 } 311 } 312 313 for (String targetContentTypeName : contentTypesToEdit) 314 { 315 ContentType targetContentType = _ctypesEP.getExtension(targetContentTypeName); 316 MetadataDefinition metadataDef = targetContentType.getMetadataDefinitionByPath(metadatapath); 317 if (metadataDef != null) 318 { 319 _setContentMetatada(contentIdsToReference, contentToEdit, targetContentType, metadatapath, workflowActionIds, errorMessages, errorIds); 320 321 return _returnValue(errorMessages, errorIds); 322 } 323 } 324 325 throw new IllegalStateException("Unable to find medatata definition to path '" + metadatapath + "'."); 326 } 327 328 private Map<String, Object> _returnValue(List<I18nizableText> errorMessages, List<String> errorIds) 329 { 330 Map<String, Object> returnValues = new HashMap<>(); 331 returnValues.put("success", errorMessages.isEmpty() && errorIds.isEmpty()); 332 if (!errorMessages.isEmpty()) 333 { 334 returnValues.put("errorMessages", errorMessages); 335 } 336 if (!errorIds.isEmpty()) 337 { 338 returnValues.put("errorIds", errorIds); 339 } 340 return returnValues; 341 } 342 343 private void _clean(List<Map<String, String>> contentsToEditToRemove, List<String> workflowActionIds, List<I18nizableText> errorMessages, List<String> errorIds) 344 { 345 for (Map<String, String> removeObject : contentsToEditToRemove) 346 { 347 String contentIdToEdit = removeObject.get("contentId"); 348 String referencingMetadataPath = removeObject.get("referencingMetadataPath"); 349 String valueToRemove = removeObject.get("valueToRemove"); 350 351 WorkflowAwareContent content = _resolver.resolveById(contentIdToEdit); 352 353 Map<String, Object> values = new HashMap<>(); 354 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + referencingMetadataPath + ".mode", MODE.REMOVE.name()); 355 356 MetadataDefinition metadataDef = _ctypesHelper.getMetadataDefinitionByMetadataValuePath(referencingMetadataPath, content); 357 if (metadataDef.isMultiple()) 358 { 359 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + referencingMetadataPath, Arrays.asList(valueToRemove)); 360 } 361 else 362 { 363 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + referencingMetadataPath, valueToRemove); 364 } 365 366 if (getLogger().isDebugEnabled()) 367 { 368 getLogger().debug("Content " + contentIdToEdit + " must be edited at " + referencingMetadataPath + " to remove " + valueToRemove); 369 } 370 371 _doAction(content, workflowActionIds, values, errorIds, errorMessages); 372 } 373 } 374 375 376 /** 377 * Set the metadata 'metadatapath' of contents 'contentIdsToEdit' with value 'contentIdsToReference' 378 * @param contentIdsToReference The list of content ids that will be added as values in the content field 379 * @param contentToEdit The map {key: contents to edit and that will have a metadata of type content modified; value: the new position if metadata is multiple and it is a reorder of values. May be null or equals to -1 if it is not a reorder} 380 * @param contentType The content type 381 * @param metadataPath The metadata selected to do modification in the contentIdsToEdit contents 382 * @param workflowActionIds The ids of workflow actions to use to edit the metadata. Actions will be tested in this order and first available action will be used 383 * @param errorMessages The list that will be felt with error messages of content that had an issue during the operation 384 * @param errorIds The list that will be felt with ids of content that had an issue during the operation 385 */ 386 protected void _setContentMetatada(List<String> contentIdsToReference, Map<WorkflowAwareContent, Integer> contentToEdit, ContentType contentType, String metadataPath, List<String> workflowActionIds, List<I18nizableText> errorMessages, List<String> errorIds) 387 { 388 // On each content 389 for (WorkflowAwareContent content : contentToEdit.keySet()) 390 { 391 Map<String, Object> values = new HashMap<>(); 392 393 String metadataName = ""; 394 String[] metadataDefPath = StringUtils.split(metadataPath, '/'); 395 396 // Find repeaters in the path 397 String lastRepeaterPath = null; 398 MetadataDefinition metadataDef = null; 399 for (String element : metadataDefPath) 400 { 401 metadataDef = metadataDef == null ? contentType.getMetadataDefinition(element) : metadataDef.getMetadataDefinition(element); 402 metadataName += ("".equals(metadataName) ? "" : ".") + metadataDef.getName(); 403 404 if (metadataDef instanceof RepeaterDefinition) 405 { 406 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".size", "1"); 407 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.INSERT.name()); 408 lastRepeaterPath = metadataName; 409 metadataName += ".1"; 410 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".position", "0"); // 0 means at the end 411 } 412 } 413 414 if (metadataDef == null) 415 { 416 throw new IllegalStateException("Definition cannot be null"); 417 } 418 419 // The value to set 420 if (metadataDef.isMultiple()) 421 { 422 Integer newPosition = contentToEdit.get(content); 423 if (newPosition == null || newPosition < 0) 424 { 425 // Normal case, it is not a move 426 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataName, contentIdsToReference); 427 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.INSERT.name()); 428 } 429 else 430 { 431 // Specific case where there is no new content id to reference, but a reorder in a multiple metadata 432 List<String> currentMetadataValue = new ArrayList<>(Arrays.asList(content.getMetadataHolder().getStringArray(metadataName))); 433 List<String> reorderedMetadataValue = _reorder(currentMetadataValue, contentIdsToReference, newPosition); 434 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataName, reorderedMetadataValue); 435 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.REPLACE.name()); 436 } 437 } 438 else if (lastRepeaterPath != null) // Special case in there is a repeater in the path and the metadata is single valued. 439 { 440 // create as many repeater entries as there is referenced contents. 441 int nbContentIdsToReference = contentIdsToReference.size(); 442 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + lastRepeaterPath + ".size", String.valueOf(nbContentIdsToReference)); 443 444 String inRepeaterPath = StringUtils.removeStart(metadataName, lastRepeaterPath + ".1"); 445 446 for (int i = 1; i <= nbContentIdsToReference; i++) 447 { 448 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + lastRepeaterPath + "." + i + inRepeaterPath, contentIdsToReference.get(i - 1)); 449 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + lastRepeaterPath + "." + i + inRepeaterPath + ".mode", MODE.INSERT.name()); 450 451 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + lastRepeaterPath + "." + i + ".position", "0"); // 0 means at the end 452 } 453 } 454 else 455 { 456 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataName, contentIdsToReference.get(0)); 457 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.INSERT.name()); 458 } 459 460 // Find the edit action to use 461 _doAction(content, workflowActionIds, values, errorIds, errorMessages); 462 } 463 } 464 465 private List<String> _reorder(List<String> currentElements, List<String> elementsToReorder, int newPosition) 466 { 467 List<String> reorderedList = new ArrayList<>(currentElements); 468 469 // 1/ in currentElements, replace the ones to reorder by null, in order to keep all indexes 470 for (int i = 0; i < currentElements.size(); i++) 471 { 472 String element = currentElements.get(i); 473 if (elementsToReorder.contains(element)) 474 { 475 reorderedList.set(i, null); 476 } 477 } 478 479 // 2/ insert the elements to reorder at the new position 480 reorderedList.addAll(newPosition, elementsToReorder); 481 482 // 3/ remove null elements, corresponding to the old positions of the elements that were reordered 483 reorderedList.removeIf(Objects::isNull); 484 485 return reorderedList; 486 } 487 488 private void _doAction(WorkflowAwareContent content, List<String> workflowActionIds, Map<String, Object> values, List<String> errorIds, List<I18nizableText> errorMessages) 489 { 490 Integer actionId = null; 491 492 int[] actionIds = _contentWorkflowHelper.getAvailableActions(content); 493 for (String workflowActionIdToTryAsString : workflowActionIds) 494 { 495 Integer workflowActionIdToTry = Integer.parseInt(workflowActionIdToTryAsString); 496 if (ArrayUtils.contains(actionIds, workflowActionIdToTry)) 497 { 498 actionId = workflowActionIdToTry; 499 break; 500 } 501 } 502 503 if (actionId == null) 504 { 505 List<String> parameters = new ArrayList<>(); 506 parameters.add(_contentHelper.getTitle(content)); 507 parameters.add(content.getName()); 508 parameters.add(content.getId()); 509 errorMessages.add(new I18nizableText("plugin.cms", "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_WORKFLOW", parameters)); 510 errorIds.add(content.getId()); 511 } 512 else 513 { 514 // edit 515 Map<String, Object> contextParameters = new HashMap<>(); 516 contextParameters.put("quit", true); 517 contextParameters.put("values", values); 518 519 Map<String, Object> inputs = new HashMap<>(); 520 inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, contextParameters); 521 522 try 523 { 524 _contentWorkflowHelper.doAction(content, actionId, inputs); 525 } 526 catch (Exception e) 527 { 528 getLogger().error("Content '" + _contentHelper.getTitle(content) + "' (" + content.getName() + "/" + content.getId() + ") was not modified", e); 529 530 Map<String, I18nizableText> parameters = new HashMap<>(); 531 parameters.put("0", new I18nizableText(_contentHelper.getTitle(content))); 532 parameters.put("1", new I18nizableText(content.getName())); 533 parameters.put("2", new I18nizableText(content.getId())); 534 535 if (e instanceof InvalidInputWorkflowException) 536 { 537 I18nizableText rootError = null; 538 539 AllErrors allErrors = ((InvalidInputWorkflowException) e).getErrors(); 540 Map<String, Errors> allErrorsMap = allErrors.getAllErrors(); 541 for (String errorMetadataPath : allErrorsMap.keySet()) 542 { 543 Errors errors = allErrorsMap.get(errorMetadataPath); 544 545 I18nizableText insideError = null; 546 547 List<I18nizableText> errorsAsList = errors.getErrors(); 548 for (I18nizableText error : errorsAsList) 549 { 550 Map<String, I18nizableText> i18nparameters = new HashMap<>(); 551 i18nparameters.put("0", error); 552 553 I18nizableText localError = new I18nizableText("plugin.cms", "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_VALIDATION_METADATA_CHAIN", i18nparameters); 554 555 if (insideError == null) 556 { 557 insideError = localError; 558 } 559 else 560 { 561 insideError.getParameterMap().put("1", localError); 562 } 563 } 564 565 Map<String, I18nizableText> i18ngeneralparameters = new HashMap<>(); 566 567 String i18ngeneralkey = null; 568 if (EditContentFunction.GLOBAL_ERROR_KEY.equals(errorMetadataPath)) 569 { 570 i18ngeneralkey = "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_GLOBAL_VALIDATION"; 571 i18ngeneralparameters.put("1", insideError); 572 } 573 else 574 { 575 i18ngeneralkey = "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_VALIDATION_METADATA"; 576 i18ngeneralparameters.put("0", new I18nizableText(errorMetadataPath)); 577 i18ngeneralparameters.put("1", insideError); 578 } 579 580 I18nizableText generalError = new I18nizableText("plugin.cms", i18ngeneralkey, i18ngeneralparameters); 581 if (rootError == null) 582 { 583 rootError = generalError; 584 } 585 else 586 { 587 rootError.getParameterMap().put("2", generalError); 588 } 589 } 590 591 parameters.put("3", rootError); 592 } 593 else 594 { 595 if (e.getMessage() != null) 596 { 597 parameters.put("3", new I18nizableText(e.getMessage())); 598 } 599 else 600 { 601 parameters.put("3", new I18nizableText(e.getClass().getName())); 602 } 603 } 604 errorMessages.add(new I18nizableText("plugin.cms", "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_EDIT", parameters)); 605 errorIds.add(content.getId()); 606 } 607 } 608 } 609 610 /** 611 * Resolve content by their ids 612 * @param contentIds The id of contents to resolve 613 * @return the contents 614 */ 615 protected List<? extends Content> _resolve(List<String> contentIds) 616 { 617 List<Content> contents = new ArrayList<>(); 618 619 for (String contentId: contentIds) 620 { 621 Content content = _resolver.resolveById(contentId); 622 contents.add(content); 623 } 624 625 return contents; 626 } 627 628 /** 629 * Resolve content by their ids 630 * @param contentIds The id of contents to resolve 631 * @return the contents 632 */ 633 protected Map<? extends Content, Integer> _resolve(Map<String, Integer> contentIds) 634 { 635 Map<Content, Integer> contents = new LinkedHashMap<>(); 636 637 for (Map.Entry<String, Integer> entry: contentIds.entrySet()) 638 { 639 Content content = _resolver.resolveById(entry.getKey()); 640 contents.put(content, entry.getValue()); 641 } 642 643 return contents; 644 } 645}