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 @Callable 276 public Map<String, Object> setContentMetatada(List<String> contentIdsToReference, Map<String, Integer> contentIdsToEdit, List<Map<String, String>> contentsToEditToRemove, String metadatapath, List<String> workflowActionIds) 277 { 278 return setContentMetatada(contentIdsToReference, contentIdsToEdit, contentsToEditToRemove, metadatapath, workflowActionIds, new HashMap<>()); 279 } 280 281 /** 282 * Set the metadata 'metadatapath' of contents 'contentIdsToEdit' with value 'contentIdsToReference' 283 * @param contentIdsToReference The list of content ids that will be added as values in the content field 284 * @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} 285 * @param contentsToEditToRemove The list of content to edit to remove currently referenced content. Keys are "contentId" and "valueToRemove" 286 * @param metadatapath The metadata path selected to do modification in the contentIdsToEdit contents 287 * @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 288 * @param additionalParams the map of additional parameters 289 * @return A map with key success: true or false. if false, it can be due to errors (list of error messages) 290 */ 291 @SuppressWarnings("unchecked") 292 @Callable 293 public Map<String, Object> setContentMetatada(List<String> contentIdsToReference, Map<String, Integer> contentIdsToEdit, List<Map<String, String>> contentsToEditToRemove, String metadatapath, List<String> workflowActionIds, Map<String, Object> additionalParams) 294 { 295 Map<WorkflowAwareContent, Integer> contentToEdit = (Map<WorkflowAwareContent, Integer>) _resolve(contentIdsToEdit); 296 297 List<String> errorIds = new ArrayList<>(); 298 List<I18nizableText> errorMessages = new ArrayList<>(); 299 300 _clean(contentsToEditToRemove, workflowActionIds, errorMessages, errorIds); 301 302 if (contentIdsToEdit.isEmpty() || contentIdsToReference.isEmpty()) 303 { 304 return _returnValue(errorMessages, errorIds); 305 } 306 307 Collection<String> contentTypesToEdit = new ArrayList<>(); 308 for (Content content: contentToEdit.keySet()) 309 { 310 Set<String> ancestorsAndMySelf = new HashSet<>(); 311 312 String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 313 for (String id : allContentTypes) 314 { 315 ancestorsAndMySelf.addAll(_ctypesHelper.getAncestors(id)); 316 ancestorsAndMySelf.add(id); 317 } 318 319 if (contentTypesToEdit.isEmpty()) 320 { 321 contentTypesToEdit = ancestorsAndMySelf; 322 } 323 else 324 { 325 contentTypesToEdit = CollectionUtils.intersection(contentTypesToEdit, ancestorsAndMySelf); 326 } 327 } 328 329 for (String targetContentTypeName : contentTypesToEdit) 330 { 331 ContentType targetContentType = _ctypesEP.getExtension(targetContentTypeName); 332 MetadataDefinition metadataDef = targetContentType.getMetadataDefinitionByPath(metadatapath); 333 if (metadataDef != null) 334 { 335 _setContentMetatada(contentIdsToReference, contentToEdit, targetContentType, metadatapath, workflowActionIds, errorMessages, errorIds, additionalParams); 336 337 return _returnValue(errorMessages, errorIds); 338 } 339 } 340 341 throw new IllegalStateException("Unable to find medatata definition to path '" + metadatapath + "'."); 342 } 343 344 private Map<String, Object> _returnValue(List<I18nizableText> errorMessages, List<String> errorIds) 345 { 346 Map<String, Object> returnValues = new HashMap<>(); 347 returnValues.put("success", errorMessages.isEmpty() && errorIds.isEmpty()); 348 if (!errorMessages.isEmpty()) 349 { 350 returnValues.put("errorMessages", errorMessages); 351 } 352 if (!errorIds.isEmpty()) 353 { 354 returnValues.put("errorIds", errorIds); 355 } 356 return returnValues; 357 } 358 359 private void _clean(List<Map<String, String>> contentsToEditToRemove, List<String> workflowActionIds, List<I18nizableText> errorMessages, List<String> errorIds) 360 { 361 for (Map<String, String> removeObject : contentsToEditToRemove) 362 { 363 String contentIdToEdit = removeObject.get("contentId"); 364 String referencingMetadataPath = removeObject.get("referencingMetadataPath"); 365 String valueToRemove = removeObject.get("valueToRemove"); 366 367 WorkflowAwareContent content = _resolver.resolveById(contentIdToEdit); 368 369 Map<String, Object> values = new HashMap<>(); 370 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + referencingMetadataPath + ".mode", MODE.REMOVE.name()); 371 372 MetadataDefinition metadataDef = _ctypesHelper.getMetadataDefinitionByMetadataValuePath(referencingMetadataPath, content); 373 if (metadataDef.isMultiple()) 374 { 375 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + referencingMetadataPath, Arrays.asList(valueToRemove)); 376 } 377 else 378 { 379 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + referencingMetadataPath, valueToRemove); 380 } 381 382 if (getLogger().isDebugEnabled()) 383 { 384 getLogger().debug("Content " + contentIdToEdit + " must be edited at " + referencingMetadataPath + " to remove " + valueToRemove); 385 } 386 387 _doAction(content, workflowActionIds, values, errorIds, errorMessages); 388 } 389 } 390 391 392 /** 393 * Set the metadata 'metadatapath' of contents 'contentIdsToEdit' with value 'contentIdsToReference' 394 * @param contentIdsToReference The list of content ids that will be added as values in the content field 395 * @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} 396 * @param contentType The content type 397 * @param metadataPath The metadata selected to do modification in the contentIdsToEdit contents 398 * @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 399 * @param errorMessages The list that will be felt with error messages of content that had an issue during the operation 400 * @param errorIds The list that will be felt with ids of content that had an issue during the operation 401 * @param additionalParams the map of additional parameters 402 */ 403 protected void _setContentMetatada(List<String> contentIdsToReference, Map<WorkflowAwareContent, Integer> contentToEdit, ContentType contentType, String metadataPath, List<String> workflowActionIds, List<I18nizableText> errorMessages, List<String> errorIds, Map<String, Object> additionalParams) 404 { 405 // On each content 406 for (WorkflowAwareContent content : contentToEdit.keySet()) 407 { 408 Map<String, Object> values = new HashMap<>(); 409 410 String metadataName = ""; 411 String[] metadataDefPath = StringUtils.split(metadataPath, '/'); 412 413 // Find repeaters in the path 414 String lastRepeaterPath = null; 415 MetadataDefinition metadataDef = null; 416 for (String element : metadataDefPath) 417 { 418 metadataDef = metadataDef == null ? contentType.getMetadataDefinition(element) : metadataDef.getMetadataDefinition(element); 419 metadataName += ("".equals(metadataName) ? "" : ".") + metadataDef.getName(); 420 421 if (metadataDef instanceof RepeaterDefinition) 422 { 423 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".size", "1"); 424 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.INSERT.name()); 425 lastRepeaterPath = metadataName; 426 metadataName += ".1"; 427 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".position", "0"); // 0 means at the end 428 } 429 } 430 431 if (metadataDef == null) 432 { 433 throw new IllegalStateException("Definition cannot be null"); 434 } 435 436 // The value to set 437 if (metadataDef.isMultiple()) 438 { 439 Integer newPosition = contentToEdit.get(content); 440 if (newPosition == null || newPosition < 0) 441 { 442 // Normal case, it is not a move 443 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataName, contentIdsToReference); 444 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.INSERT.name()); 445 } 446 else 447 { 448 // Specific case where there is no new content id to reference, but a reorder in a multiple metadata 449 List<String> currentMetadataValue = new ArrayList<>(Arrays.asList(content.getMetadataHolder().getStringArray(metadataName))); 450 List<String> reorderedMetadataValue = _reorder(currentMetadataValue, contentIdsToReference, newPosition); 451 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataName, reorderedMetadataValue); 452 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.REPLACE.name()); 453 } 454 } 455 else if (lastRepeaterPath != null) // Special case in there is a repeater in the path and the metadata is single valued. 456 { 457 // create as many repeater entries as there is referenced contents. 458 int nbContentIdsToReference = contentIdsToReference.size(); 459 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + lastRepeaterPath + ".size", String.valueOf(nbContentIdsToReference)); 460 461 String inRepeaterPath = StringUtils.removeStart(metadataName, lastRepeaterPath + ".1"); 462 463 for (int i = 1; i <= nbContentIdsToReference; i++) 464 { 465 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + lastRepeaterPath + "." + i + inRepeaterPath, contentIdsToReference.get(i - 1)); 466 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + lastRepeaterPath + "." + i + inRepeaterPath + ".mode", MODE.INSERT.name()); 467 468 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + lastRepeaterPath + "." + i + ".position", "0"); // 0 means at the end 469 } 470 } 471 else 472 { 473 values.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataName, contentIdsToReference.get(0)); 474 values.put(EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metadataName + ".mode", MODE.INSERT.name()); 475 } 476 477 // Find the edit action to use 478 _doAction(content, workflowActionIds, values, errorIds, errorMessages); 479 } 480 } 481 482 private List<String> _reorder(List<String> currentElements, List<String> elementsToReorder, int newPosition) 483 { 484 List<String> reorderedList = new ArrayList<>(currentElements); 485 486 // 1/ in currentElements, replace the ones to reorder by null, in order to keep all indexes 487 for (int i = 0; i < currentElements.size(); i++) 488 { 489 String element = currentElements.get(i); 490 if (elementsToReorder.contains(element)) 491 { 492 reorderedList.set(i, null); 493 } 494 } 495 496 // 2/ insert the elements to reorder at the new position 497 reorderedList.addAll(newPosition, elementsToReorder); 498 499 // 3/ remove null elements, corresponding to the old positions of the elements that were reordered 500 reorderedList.removeIf(Objects::isNull); 501 502 return reorderedList; 503 } 504 505 private void _doAction(WorkflowAwareContent content, List<String> workflowActionIds, Map<String, Object> values, List<String> errorIds, List<I18nizableText> errorMessages) 506 { 507 Integer actionId = null; 508 509 int[] actionIds = _contentWorkflowHelper.getAvailableActions(content); 510 for (String workflowActionIdToTryAsString : workflowActionIds) 511 { 512 Integer workflowActionIdToTry = Integer.parseInt(workflowActionIdToTryAsString); 513 if (ArrayUtils.contains(actionIds, workflowActionIdToTry)) 514 { 515 actionId = workflowActionIdToTry; 516 break; 517 } 518 } 519 520 if (actionId == null) 521 { 522 List<String> parameters = new ArrayList<>(); 523 parameters.add(_contentHelper.getTitle(content)); 524 parameters.add(content.getName()); 525 parameters.add(content.getId()); 526 errorMessages.add(new I18nizableText("plugin.cms", "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_WORKFLOW", parameters)); 527 errorIds.add(content.getId()); 528 } 529 else 530 { 531 // edit 532 Map<String, Object> contextParameters = new HashMap<>(); 533 contextParameters.put("quit", true); 534 contextParameters.put("values", values); 535 536 Map<String, Object> inputs = new HashMap<>(); 537 inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, contextParameters); 538 539 try 540 { 541 _contentWorkflowHelper.doAction(content, actionId, inputs); 542 } 543 catch (Exception e) 544 { 545 getLogger().error("Content '" + _contentHelper.getTitle(content) + "' (" + content.getName() + "/" + content.getId() + ") was not modified", e); 546 547 Map<String, I18nizableText> parameters = new HashMap<>(); 548 parameters.put("0", new I18nizableText(_contentHelper.getTitle(content))); 549 parameters.put("1", new I18nizableText(content.getName())); 550 parameters.put("2", new I18nizableText(content.getId())); 551 552 if (e instanceof InvalidInputWorkflowException) 553 { 554 I18nizableText rootError = null; 555 556 AllErrors allErrors = ((InvalidInputWorkflowException) e).getErrors(); 557 Map<String, Errors> allErrorsMap = allErrors.getAllErrors(); 558 for (String errorMetadataPath : allErrorsMap.keySet()) 559 { 560 Errors errors = allErrorsMap.get(errorMetadataPath); 561 562 I18nizableText insideError = null; 563 564 List<I18nizableText> errorsAsList = errors.getErrors(); 565 for (I18nizableText error : errorsAsList) 566 { 567 Map<String, I18nizableText> i18nparameters = new HashMap<>(); 568 i18nparameters.put("0", error); 569 570 I18nizableText localError = new I18nizableText("plugin.cms", "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_VALIDATION_METADATA_CHAIN", i18nparameters); 571 572 if (insideError == null) 573 { 574 insideError = localError; 575 } 576 else 577 { 578 insideError.getParameterMap().put("1", localError); 579 } 580 } 581 582 Map<String, I18nizableText> i18ngeneralparameters = new HashMap<>(); 583 584 String i18ngeneralkey = null; 585 if (EditContentFunction.GLOBAL_ERROR_KEY.equals(errorMetadataPath)) 586 { 587 i18ngeneralkey = "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_GLOBAL_VALIDATION"; 588 i18ngeneralparameters.put("1", insideError); 589 } 590 else 591 { 592 i18ngeneralkey = "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_VALIDATION_METADATA"; 593 i18ngeneralparameters.put("0", new I18nizableText(errorMetadataPath)); 594 i18ngeneralparameters.put("1", insideError); 595 } 596 597 I18nizableText generalError = new I18nizableText("plugin.cms", i18ngeneralkey, i18ngeneralparameters); 598 if (rootError == null) 599 { 600 rootError = generalError; 601 } 602 else 603 { 604 rootError.getParameterMap().put("2", generalError); 605 } 606 } 607 608 parameters.put("3", rootError); 609 } 610 else 611 { 612 if (e.getMessage() != null) 613 { 614 parameters.put("3", new I18nizableText(e.getMessage())); 615 } 616 else 617 { 618 parameters.put("3", new I18nizableText(e.getClass().getName())); 619 } 620 } 621 errorMessages.add(new I18nizableText("plugin.cms", "PLUGINS_CMS_RELATIONS_SETCONTENTMETADATA_REFERENCE_ERROR_EDIT", parameters)); 622 errorIds.add(content.getId()); 623 } 624 } 625 } 626 627 /** 628 * Resolve content by their ids 629 * @param contentIds The id of contents to resolve 630 * @return the contents 631 */ 632 protected List<? extends Content> _resolve(List<String> contentIds) 633 { 634 List<Content> contents = new ArrayList<>(); 635 636 for (String contentId: contentIds) 637 { 638 Content content = _resolver.resolveById(contentId); 639 contents.add(content); 640 } 641 642 return contents; 643 } 644 645 /** 646 * Resolve content by their ids 647 * @param contentIds The id of contents to resolve 648 * @return the contents 649 */ 650 protected Map<? extends Content, Integer> _resolve(Map<String, Integer> contentIds) 651 { 652 Map<Content, Integer> contents = new LinkedHashMap<>(); 653 654 for (Map.Entry<String, Integer> entry: contentIds.entrySet()) 655 { 656 Content content = _resolver.resolveById(entry.getKey()); 657 contents.put(content, entry.getValue()); 658 } 659 660 return contents; 661 } 662}