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.workflow; 017 018import java.io.ByteArrayInputStream; 019import java.io.IOException; 020import java.io.UnsupportedEncodingException; 021import java.lang.reflect.Array; 022import java.time.LocalDate; 023import java.time.LocalDateTime; 024import java.time.format.DateTimeFormatter; 025import java.time.format.DateTimeParseException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.Date; 031import java.util.HashMap; 032import java.util.HashSet; 033import java.util.Iterator; 034import java.util.LinkedHashSet; 035import java.util.List; 036import java.util.Map; 037import java.util.Map.Entry; 038import java.util.NoSuchElementException; 039import java.util.Set; 040import java.util.TreeMap; 041 042import javax.jcr.Node; 043import javax.jcr.RepositoryException; 044import javax.jcr.lock.Lock; 045import javax.jcr.lock.LockManager; 046 047import org.apache.avalon.framework.activity.Initializable; 048import org.apache.commons.collections.CollectionUtils; 049import org.apache.commons.collections.comparators.ReverseComparator; 050import org.apache.commons.lang3.ArrayUtils; 051import org.apache.commons.lang3.LocaleUtils; 052import org.apache.commons.lang3.StringUtils; 053import org.apache.commons.lang3.math.NumberUtils; 054 055import org.ametys.cms.ObservationConstants; 056import org.ametys.cms.content.external.ExternalizableMetadataHelper; 057import org.ametys.cms.content.external.ExternalizableMetadataProvider.ExternalizableMetadataStatus; 058import org.ametys.cms.content.external.ExternalizableMetadataProviderExtensionPoint; 059import org.ametys.cms.content.references.OutgoingReferences; 060import org.ametys.cms.content.references.OutgoingReferencesExtractor; 061import org.ametys.cms.contenttype.AbstractMetadataSetElement; 062import org.ametys.cms.contenttype.ContentConstants; 063import org.ametys.cms.contenttype.ContentType; 064import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 065import org.ametys.cms.contenttype.ContentTypesHelper; 066import org.ametys.cms.contenttype.ContentValidator; 067import org.ametys.cms.contenttype.MetadataDefinition; 068import org.ametys.cms.contenttype.MetadataDefinitionReference; 069import org.ametys.cms.contenttype.MetadataSet; 070import org.ametys.cms.contenttype.MetadataType; 071import org.ametys.cms.contenttype.RepeaterDefinition; 072import org.ametys.cms.form.AbstractField; 073import org.ametys.cms.form.AbstractField.MODE; 074import org.ametys.cms.form.BinaryField; 075import org.ametys.cms.form.ExternalizableField; 076import org.ametys.cms.form.Form; 077import org.ametys.cms.form.ReferenceField; 078import org.ametys.cms.form.RepeaterField; 079import org.ametys.cms.form.RepeaterField.RepeaterEntry; 080import org.ametys.cms.form.RichTextField; 081import org.ametys.cms.form.SimpleField; 082import org.ametys.cms.form.SubContentField; 083import org.ametys.cms.repository.Content; 084import org.ametys.cms.repository.ModifiableContent; 085import org.ametys.cms.repository.WorkflowAwareContent; 086import org.ametys.cms.transformation.RichTextTransformer; 087import org.ametys.core.observation.Event; 088import org.ametys.core.observation.ObservationManager; 089import org.ametys.core.upload.Upload; 090import org.ametys.core.upload.UploadManager; 091import org.ametys.core.user.User; 092import org.ametys.core.user.UserIdentity; 093import org.ametys.core.user.UserManager; 094import org.ametys.core.util.DateUtils; 095import org.ametys.core.util.JSONUtils; 096import org.ametys.plugins.core.user.UserHelper; 097import org.ametys.plugins.repository.AmetysObject; 098import org.ametys.plugins.repository.AmetysObjectIterable; 099import org.ametys.plugins.repository.AmetysObjectResolver; 100import org.ametys.plugins.repository.AmetysRepositoryException; 101import org.ametys.plugins.repository.RepositoryConstants; 102import org.ametys.plugins.repository.TraversableAmetysObject; 103import org.ametys.plugins.repository.UnknownAmetysObjectException; 104import org.ametys.plugins.repository.lock.LockHelper; 105import org.ametys.plugins.repository.lock.LockableAmetysObject; 106import org.ametys.plugins.repository.metadata.BinaryMetadata; 107import org.ametys.plugins.repository.metadata.CommentableCompositeMetadata; 108import org.ametys.plugins.repository.metadata.CompositeMetadata; 109import org.ametys.plugins.repository.metadata.MetadataComment; 110import org.ametys.plugins.repository.metadata.ModifiableBinaryMetadata; 111import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 112import org.ametys.plugins.repository.metadata.ModifiableFolder; 113import org.ametys.plugins.repository.metadata.ModifiableResource; 114import org.ametys.plugins.repository.metadata.ModifiableRichText; 115import org.ametys.plugins.repository.metadata.MultilingualString; 116import org.ametys.plugins.repository.metadata.Resource; 117import org.ametys.plugins.repository.metadata.UnknownMetadataException; 118import org.ametys.plugins.repository.metadata.jcr.JCRCompositeMetadata; 119import org.ametys.plugins.workflow.AbstractWorkflowComponent; 120import org.ametys.plugins.workflow.component.CheckRightsCondition; 121import org.ametys.plugins.workflow.support.WorkflowProvider; 122import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 123import org.ametys.runtime.config.Config; 124import org.ametys.runtime.i18n.I18nizableText; 125import org.ametys.runtime.parameter.Errors; 126import org.ametys.runtime.parameter.Validator; 127 128import com.opensymphony.module.propertyset.PropertySet; 129import com.opensymphony.workflow.FunctionProvider; 130import com.opensymphony.workflow.WorkflowException; 131 132/** 133 * OSWorkflow function to edit a content. 134 * 135 * The required transient variables: 136 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY - Map<String, Object> The map containing the results of the function. 137 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY.result - String "true" when everything goes fine. Missing in other case. 138 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY.<MetadataPath> - Errors Each error during edition will be set here. Key will be the metadata path (with '.' separator). Value will be the error message. 139 * - AbstractContentWorkflowComponent.CONTENT_KEY - WorkflowAwareContent The content that will be edited. Should have the lock token. 140 * - AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY - Map<String, Object> Contains the following parameters: 141 * - AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY.QUIT - boolean True to specify edition mode will be quit, this imply to unlock the content. 142 * - AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY.METADATA_SET_PARAM The name of the edition metadata set to use and to check metadata. If missing a metadataset will be created. 143 * - AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY.FORM_RAW_VALUES - Map<String, Object> The values of the submitted form : 144 * - AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY.FORM_RAW_VALUES.<MetadataPath> Object Key is the path of the metadata ('.' separated) prefixed by FORM_ELEMENTS_PREFIX. Value is a depending on the type of metadata. 145 * Sometimes types require additional information. In that case : Key is a metadata path ('.' separated) prefixed by INTERNAL_FORM_ELEMENTS_PREFIX and suffixed by '.' + an additional information name. 146 * - AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY.FORM_RAW_COMMENTS - Map<String, List<Map<String, String>>> The comments of the metadata of the submitted form : 147 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY.<MetadataPath> - List<Map<String, String>> Key is the path of the metadata ('.' separated) prefixed by FORM_ELEMENTS_PREFIX. Value is the list of comments. 148 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY.<MetadataPath>.<X> - <Map<String, String> A comment with the following parameters 149 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY.<MetadataPath>.<X>.author String The login of the author of the comment 150 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY.<MetadataPath>.<X>.text String The text of the comment 151 * - AbstractContentWorkflowComponent.RESULT_MAP_KEY.<MetadataPath>.<X>.date String The date of the comment using the ISODateTimeFormat (See DateUtils.parse) 152 * 153 * Where <MetadataPath> is the path of a metadata (using a '.' separator). In some cases it is prefixed by FORM_ELEMENTS_PREFIX. A metadata path with in a repeater include the number of the repeated instance (1 based). 154 * Where <X> Is an element of the parent list. 155 * 156 * Here is the list of required information and values depending on the type of the metadata: 157 * - MetadataType.STRING 158 * When simple, the value is a String containing the value. Ex: "content.input.abstract": "Sample of abstract" 159 * When multiple, the value is a String[] containing the values. Ex: "content.input.abstract": ["value 1", "value 2"]. An additional information can be provided: "mode" that can be "replace" (default value) or "insert". When "replace", the given String[] will replace the existing value. When "insert"; the given String[] will be appended. Ex: "_content.input.abstract.mode": "insert". 160 * - MetadataType.DATE 161 * Same as MetadataType.STRING where values are String encoded at ISODateTimeFormat (See DateUtils.parse). Ex: "content.input.date": "2014-03-12T00:00:00.000+01:00". 162 * - MetadataType.DATETIME 163 * See MetadataType.DATE. 164 * - MetadataType.LONG 165 * Same as MetadataType.STRING where values are String that can be parsed as Long. 166 * - MetadataType.DOUBLE 167 * Same as MetadataType.STRING where values are String that can be parsed as Double. 168 * - MetadataType.BOOLEAN 169 * Same as MetadataType.STRING where values are String that can be parsed as Boolean. 170 * - MetadataType.GEOCODE 171 * A single String that is are a map encoded with 2 keys 'latitude' and 'longitude' and double values. Ex: "content.input.address.gps": "{\"latitude\":1.574576,\"longitude\":103.79255799999999}" 172 * - MetadataType.COMPOSITE 173 * A composite metadata does not have values itself. The values are sub metadata. 174 * A repeater (sort of multiple composite) does not have value either, but do have additional information: 175 * "size" in a String representing a Long with the number of elements submitted. Ex: "_content.input.attachments.size": "2". This information is crucial since this function only handle repeated values going from 1 to size. Ex: "content.input.attachments.1.*" and "content.input.attachments.2.*". 176 * "mode" as for MetadataType.STRING, mode can be "replace" (default value) to remove all current entries before adding new one, or "insert" to add only the new values (see "position" under to see where to insert). 177 * Each instance in the repeater does also have additional information. 178 * "previous-position" As explained above, a metadatapath inside a repeater include the current position of the element. This value is a String encoding a Long with the position of the repeater instance BEFORE this edition: this allow to move the repeater instance, instead of removing it and add it again (specially with FILE or BINARY metadata). Ex: "_content.input.attachments.1.previous-position": "1". Will be "-1" when it is a new instance. 179 * "position" In "insert" mode, we do not want to use the value set in the metadata path as a "current position". The position given by path is wrong as it always start at 1 and finish at size. Values >= 1 are positions. Ex: "_content.input.attachments.1.position": "20", means that all values "content.input.attachments.1.*" will be added to an instance at the position "20" (and not "1" as it will be in "replace" mode). Values < 1 are positions indexed by the end. 0 means to add it to the end. -1 to add it just before the last one. In "insert" mode, elements are insersected at the given positions and do not replace those elements. Inserting 5 elements at "position" "1", will insert 5 elements at position 1, 2, 3, 4 and 5. 180 * - MetadataType.BINARY 181 * TODO 182 * - MetadataType.RICH_TEXT 183 * TODO 184 * - MetadataType.USER 185 * A single String that is a map encoded with 2 keys 'login' and 'populationId' and string values. Ex: "content.input.user": "{\"login\":"alogin",\"populationId\":"apopulation"}" 186 * - MetadataType.REFERENCE 187 * When simple, a single String that is a map encoded with 2 keys 'value' and 'type', both are string. 'type' is the type of the reference (ex: external-url), and 'value' its value. 188 * When multiple, an Array of String. Where each string is formatted the same way as done in the simple case. 189 * - MetadataType.CONTENT 190 * TODO 191 * - MetadataType.SUB_CONTENT 192 * TODO 193 * - MetadataType.FILE 194 * TODO 195 * TODO talk about UNTOUCHED_BINARY UNTOUCHED_FILE METADATA_FILE EXPLORER_FILE 196 */ 197public class EditContentFunction extends AbstractContentWorkflowComponent implements FunctionProvider, Initializable 198{ 199 /** Constant for storing the action id for editing revert relations. */ 200 public static final String INVERT_RELATION_EDIT_WORKFLOW_ACTION_ID = EditContentFunction.class.getName() + "$invertEditActionId"; 201 202 /** Constant for storing the action id for editing revert relations. */ 203 public static final String EDIT_MUTUAL_RELATIONSHIP = EditContentFunction.class.getName() + "$mutualRelationship"; 204 205 /** Prefix for HTML form elements. */ 206 public static final String FORM_ELEMENTS_PREFIX = "content.input."; 207 /** The key for global errors */ 208 public static final String GLOBAL_ERROR_KEY = "_global"; 209 /** Prefix for internal HTML form elements. */ 210 public static final String INTERNAL_FORM_ELEMENTS_PREFIX = "_" + FORM_ELEMENTS_PREFIX; 211 /** Request parameter key for the field values. */ 212 public static final String FORM_RAW_VALUES = "values"; 213 /** Request parameter key for the field comments. */ 214 public static final String FORM_RAW_COMMENTS = "comments"; 215 /** Prefix for the metadata set name request parameter. */ 216 public static final String METADATA_SET_PARAM = "content.metadata.set"; 217 /** Prefix for the quit edition mode request parameter. */ 218 public static final String QUIT = "quit"; 219 /** Constant for untouched binary metadata. */ 220 public static final String UNTOUCHED_BINARY = "untouched"; 221 /** Constant for untouched file metadata. */ 222 public static final String UNTOUCHED_FILE = "untouched"; 223 /** Constant for local file metadata. */ 224 public static final String METADATA_FILE = "metadata"; 225 /** Constant for shared file metadata. */ 226 public static final String EXPLORER_FILE = "explorer"; 227 228 /** Default action id of editing revert relations. */ 229 private static final int __INVERT_EDIT_ACTION_ID = 2; 230 231 /** The AmetysObject resolver. */ 232 protected AmetysObjectResolver _resolver; 233 /** Content type extension point. */ 234 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 235 /** Helper for content types */ 236 protected ContentTypesHelper _contentTypesHelper; 237 /** Upload manager. */ 238 protected UploadManager _uploadManager; 239 /** Observation manager available to subclasses. */ 240 protected ObservationManager _observationManager; 241 /** The JSON conversion utilities. */ 242 protected JSONUtils _jsonUtils; 243 /** The workflow provider */ 244 protected WorkflowProvider _workflowProvider; 245 /** The content workflow helper. */ 246 protected ContentWorkflowHelper _workflowHelper; 247 /** The outgoing references extractor */ 248 protected OutgoingReferencesExtractor _outgoingReferencesExtractor; 249 /** The user manager */ 250 protected UserManager _userManager; 251 /** The user helper */ 252 protected UserHelper _userHelper; 253 /** Provider for externalizable metadata */ 254 protected ExternalizableMetadataProviderExtensionPoint _externalizableMetadataProviderEP; 255 /** Set of already checked node */ 256 protected Set<String> _lockAlreadyChecked = new HashSet<>(); 257 258 @Override 259 public void initialize() throws Exception 260 { 261 _resolver = (AmetysObjectResolver) _manager.lookup(AmetysObjectResolver.ROLE); 262 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) _manager.lookup(ContentTypeExtensionPoint.ROLE); 263 _uploadManager = (UploadManager) _manager.lookup(UploadManager.ROLE); 264 _observationManager = (ObservationManager) _manager.lookup(ObservationManager.ROLE); 265 _jsonUtils = (JSONUtils) _manager.lookup(JSONUtils.ROLE); 266 _workflowProvider = (WorkflowProvider) _manager.lookup(WorkflowProvider.ROLE); 267 _workflowHelper = (ContentWorkflowHelper) _manager.lookup(ContentWorkflowHelper.ROLE); 268 _contentTypesHelper = (ContentTypesHelper) _manager.lookup(ContentTypesHelper.ROLE); 269 _outgoingReferencesExtractor = (OutgoingReferencesExtractor) _manager.lookup(OutgoingReferencesExtractor.ROLE); 270 _userManager = (UserManager) _manager.lookup(UserManager.ROLE); 271 _userHelper = (UserHelper) _manager.lookup(UserHelper.ROLE); 272 _externalizableMetadataProviderEP = (ExternalizableMetadataProviderExtensionPoint) _manager.lookup(ExternalizableMetadataProviderExtensionPoint.ROLE); 273 } 274 275 @SuppressWarnings("unchecked") 276 @Override 277 public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException 278 { 279 _logger.info("Performing edit workflow function"); 280 281 _lockAlreadyChecked = new HashSet<>(); 282 283 // Retrieve current content 284 WorkflowAwareContent content = getContent(transientVars); 285 UserIdentity user = getUser(transientVars); 286 287 // Get the action id for editing invert relations 288 int invertEditActionId = transientVars.containsKey(INVERT_RELATION_EDIT_WORKFLOW_ACTION_ID) ? (Integer) transientVars.get(INVERT_RELATION_EDIT_WORKFLOW_ACTION_ID) : __INVERT_EDIT_ACTION_ID; 289 290 if (!(content instanceof ModifiableContent)) 291 { 292 throw new IllegalArgumentException("The provided content " + content.getId() + " is not a ModifiableContent."); 293 } 294 295 ModifiableContent modifiableContent = (ModifiableContent) content; 296 297 try 298 { 299 LockableAmetysObject lockableContent = null; 300 if (content instanceof LockableAmetysObject) 301 { 302 lockableContent = (LockableAmetysObject) content; 303 if (lockableContent.isLocked() && !LockHelper.isLockOwner(lockableContent, user)) 304 { 305 throw new WorkflowException("User '" + user + "' try to save content '" + modifiableContent.getName() + "' but it is locked by another user"); 306 } 307 } 308 309 AllErrors errors = new AllErrors(); 310 311 Map<String, Object> parameters = getContextParameters(transientVars); 312 313 long time_0 = System.currentTimeMillis(); 314 315 Map<String, Object> rawValues = (Map<String, Object>) parameters.get(FORM_RAW_VALUES); 316 MetadataSet metadataSet = getMetadataSet(parameters, rawValues, modifiableContent); 317 318 long time_1 = System.currentTimeMillis(); 319 320 Map<String, List<Map<String, String>>> rawComments = (Map<String, List<Map<String, String>>>) parameters.get(FORM_RAW_COMMENTS); 321 322 Set<String> externalAndLocalMetadata = _externalizableMetadataProviderEP.getExternalAndLocalMetadata(modifiableContent); 323 324 _bindAndValidateContent(modifiableContent, errors, metadataSet, rawValues, rawComments, user, invertEditActionId, externalAndLocalMetadata); 325 326 long time_2 = System.currentTimeMillis(); 327 328 _handleErrors(transientVars, modifiableContent, errors); 329 330 _updateCommonMetadata(modifiableContent, user); 331 332 _extractOutgoingReferences(modifiableContent); 333 334 long time_3 = System.currentTimeMillis(); 335 336 // Commit changes 337 modifiableContent.saveChanges(); 338 339 long time_4 = System.currentTimeMillis(); 340 341 // Notify the observers of the modification. 342 _notifyContentModified(content, transientVars); 343 344 long time_5 = System.currentTimeMillis(); 345 346 // Unlock content if we are not in save & quit mode 347 boolean quit = (Boolean) parameters.get(QUIT); 348 if (quit && lockableContent != null && lockableContent.isLocked()) 349 { 350 lockableContent.unlock(); 351 } 352 353 long time_6 = System.currentTimeMillis(); 354 355 boolean logAbnormalTime = Config.getInstance().getValue("runtime.log.abnormal.time"); 356 if (time_6 - time_0 > 5000 && logAbnormalTime) 357 { 358 _logger.warn("Edit content action has taken an abnormally long time : get metadata set in " + (time_1 - time_0) + " ms / bind metadata in " + (time_2 - time_1) + " ms / build consistencies in " + (time_3 - time_2) + " ms / save in " + (time_4 - time_3) + " / notify listeners in " + (time_5 - time_4) + " / total in " + (time_6 - time_0) + " ms"); 359 } 360 else if (_logger.isDebugEnabled()) 361 { 362 _logger.debug("Edit timers : get metadata set in " + (time_1 - time_0) + " ms / bind metadata in " + (time_2 - time_1) + " ms / build consistencies in " + (time_3 - time_2) + " ms / save in " + (time_4 - time_3) + " / notify listeners in " + (time_5 - time_4) + " / total in " + (time_6 - time_0) + " ms"); 363 } 364 365 getResultsMap(transientVars).put("result", "ok"); 366 } 367 catch (AmetysRepositoryException e) 368 { 369 throw new WorkflowException("Unable to edit content " + modifiableContent + " from the repository", e); 370 } 371 } 372 373 private void _handleErrors(Map transientVars, ModifiableContent modifiableContent, AllErrors errors) throws WorkflowException, InvalidInputWorkflowException 374 { 375 if (errors.hasErrors()) 376 { 377 // Populate the map to render 378 Map<String, Object> result = getResultsMap(transientVars); 379 380 Map<String, I18nizableText> errorFieldLabels = new HashMap<>(); 381 382 for (Map.Entry<String, Errors> entry : errors.getAllErrors().entrySet()) 383 { 384 String canonicalMetadataPath = entry.getKey().replace('/', '.'); 385 386 result.put(canonicalMetadataPath, entry.getValue()); 387 388 MetadataDefinition metadataDefinition = _contentTypesHelper.getMetadataDefinition(entry.getKey(), modifiableContent); 389 if (metadataDefinition != null) 390 { 391 errorFieldLabels.put(canonicalMetadataPath, metadataDefinition.getLabel()); 392 } 393 } 394 395 result.put("errorFieldLabels", errorFieldLabels); 396 397 throw new InvalidInputWorkflowException("At least one validation error is preventing from saving the modifications", errors); 398 } 399 } 400 401 /** 402 * Notify observers that the content has been modified 403 * @param content The content modified 404 * @param transientVars The workflow vars 405 * @throws WorkflowException If an error occurred 406 */ 407 protected void _notifyContentModified(Content content, Map transientVars) throws WorkflowException 408 { 409 Map<String, Object> eventParams = new HashMap<>(); 410 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 411 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 412 413 if (transientVars.containsKey(EditContentFunction.EDIT_MUTUAL_RELATIONSHIP)) 414 { 415 eventParams.put(EditContentFunction.EDIT_MUTUAL_RELATIONSHIP, true); 416 } 417 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, getUser(transientVars), eventParams)); 418 } 419 420 /** 421 * Get the metadata set for the content 422 * @param jsParameters The request parameters 423 * @param rawValues The raw values of the form 424 * @param content The content 425 * @return The metadata set asked in the request or a built-in metadataset 426 * @throws WorkflowException If an error occurred while getting the metadata set 427 */ 428 protected MetadataSet getMetadataSet(Map<String, Object> jsParameters, Map<String, Object> rawValues, Content content) throws WorkflowException 429 { 430 MetadataSet metadataSet; 431 432 String metadataSetName = (String) jsParameters.get(METADATA_SET_PARAM); 433 if (metadataSetName != null) 434 { 435 metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); 436 if (metadataSet == null) 437 { 438 throw new WorkflowException(String.format("No metadata set for name '%s' and content type(s) '%s'", metadataSetName, StringUtils.join(content.getTypes(), ','))); 439 } 440 } 441 else 442 { 443 // Let us compute a metadataset 444 metadataSet = _createMetadataSet(rawValues); 445 } 446 447 return metadataSet; 448 } 449 450 private MetadataSet _createMetadataSet(Map<String, Object> rawValues) 451 { 452 MetadataSet metadataSet; 453 metadataSet = new MetadataSet(); 454 metadataSet.setName("__generated__"); 455 metadataSet.setLabel(new I18nizableText("Live edition metadataset")); 456 metadataSet.setDescription(new I18nizableText("Live edition metadataset")); 457 metadataSet.setSmallIcon(null); 458 metadataSet.setMediumIcon(null); 459 metadataSet.setLargeIcon(null); 460 metadataSet.setEdition(true); 461 metadataSet.setInternal(true); 462 463 // Lets remove numbers in repeaters definitions 464 @SuppressWarnings("unchecked") 465 Map<String, Integer> sizePrefixes = new TreeMap<>(new ReverseComparator()); 466 for (String parameterName : rawValues.keySet()) 467 { 468 if (StringUtils.startsWith(parameterName, INTERNAL_FORM_ELEMENTS_PREFIX) && StringUtils.endsWith(parameterName, ".size")) 469 { 470 String prefix = parameterName.substring(1, parameterName.length() - 5); // 5 is ".size" length 471 Integer size = Integer.parseInt((String) rawValues.get(parameterName)); 472 473 sizePrefixes.put(prefix, size); 474 } 475 } 476 477 Set<String> parameterNames2MetadataDefPaths = new HashSet<>(rawValues.keySet()); 478 boolean setWasModified = true; 479 while (setWasModified) 480 { 481 setWasModified = false; 482 483 Iterator<String> sizePrefixesIterator = sizePrefixes.keySet().iterator(); 484 while (!setWasModified && sizePrefixesIterator.hasNext()) 485 { 486 String sizePrefix = sizePrefixesIterator.next(); 487 488 for (int i = 1; !setWasModified && i <= sizePrefixes.get(sizePrefix); i++) 489 { 490 String numPrefix = "." + i + "."; 491 String prefix = sizePrefix + numPrefix; 492 493 Iterator<String> parameterNames2MetadataDefPathsIterator = parameterNames2MetadataDefPaths.iterator(); 494 while (!setWasModified && parameterNames2MetadataDefPathsIterator.hasNext()) 495 { 496 String parameterNames2MetadataDefPath = parameterNames2MetadataDefPathsIterator.next(); 497 498 if (StringUtils.startsWith(parameterNames2MetadataDefPath, prefix)) 499 { 500 parameterNames2MetadataDefPaths.remove(parameterNames2MetadataDefPath); 501 502 String newName = parameterNames2MetadataDefPath.substring(0, prefix.length() - numPrefix.length()) + parameterNames2MetadataDefPath.substring(prefix.length() - 1); 503 parameterNames2MetadataDefPaths.add(newName); 504 505 setWasModified = true; 506 } 507 } 508 } 509 } 510 } 511 512 for (String parameterName : parameterNames2MetadataDefPaths) 513 { 514 if (parameterName.startsWith(FORM_ELEMENTS_PREFIX)) 515 { 516 String metadataName = parameterName.substring(FORM_ELEMENTS_PREFIX.length()); 517 _addMetadataDefRef(metadataSet, metadataName); 518 } 519 } 520 return metadataSet; 521 } 522 523 private void _addMetadataDefRef(AbstractMetadataSetElement metadataSetElement, String metadataName) 524 { 525 String currentLevelMetadataName = StringUtils.substringBefore(metadataName, "."); 526 MetadataDefinitionReference metaDefRef = metadataSetElement.getMetadataDefinitionReference(currentLevelMetadataName); 527 if (metaDefRef == null) 528 { 529 metaDefRef = new MetadataDefinitionReference(currentLevelMetadataName, "main"); 530 metadataSetElement.addElement(metaDefRef); 531 } 532 533 String subLevelMetadataName = StringUtils.substringAfter(metadataName, "."); 534 if (StringUtils.isNotBlank(subLevelMetadataName)) 535 { 536 _addMetadataDefRef(metaDefRef, subLevelMetadataName); 537 } 538 } 539 540 /** 541 * Analyze the content to extract outgoing references and store them 542 * @param content The content to analyze 543 */ 544 protected void _extractOutgoingReferences(ModifiableContent content) 545 { 546 Map<String, OutgoingReferences> outgoingReferencesByPath = _outgoingReferencesExtractor.getOutgoingReferences(content); 547 content.setOutgoingReferences(outgoingReferencesByPath); 548 } 549 550 /** 551 * Template method to indicates if invert relation should be taken into account during the whole edition. 552 * Override and return false to disabled invert relation management. 553 * @return true if invert relation are enabled 554 */ 555 protected boolean _invertRelationEnabled() 556 { 557 return true; 558 } 559 560 /** 561 * Bind and validate a form. 562 * @param content the content. 563 * @param allErrors the errors. 564 * @param metadataSet the metadataset 565 * @param rawValues the raw values of the form 566 * @param rawComments the raw comments of the form 567 * @param user the user. 568 * @param invertEditActionId The current 'edit content' action ID. 569 * @param externalAndLocalMetadata The paths of externalizable metadata 570 * @throws WorkflowException if an error occurs. 571 */ 572 protected void _bindAndValidateContent(ModifiableContent content, AllErrors allErrors, MetadataSet metadataSet, Map<String, Object> rawValues, Map<String, List<Map<String, String>>> rawComments, UserIdentity user, int invertEditActionId, Set<String> externalAndLocalMetadata) throws WorkflowException 573 { 574 Form form = new Form(); 575 576 // First bind and validate in the form object 577 _bindAndValidateMetadataSetElement(content, form, allErrors, metadataSet, rawValues, rawComments, null, "", externalAndLocalMetadata); 578 579 // Additional validation 580 _validateForm(content, form, metadataSet, allErrors); 581 582 // Do not synchronize if there is at least one error 583 if (!allErrors.hasErrors()) 584 { 585 // Prepare to synchronize 586 _prepareSynchronizeMetadataSetElement(content, content.getMetadataHolder(), form, allErrors, user, metadataSet, null, "", invertEditActionId); 587 588 if (!allErrors.hasErrors()) 589 { 590 // Synchronize form fields and content metadata if no error 591 _synchronizeMetadataSetElement(content, content.getMetadataHolder(), form, allErrors, user, metadataSet, null, "", invertEditActionId, externalAndLocalMetadata); 592 } 593 594 } 595 } 596 597 /** 598 * Bind and validate a metadata set element. 599 * @param content the content. 600 * @param form the form. 601 * @param allErrors the errors. 602 * @param metadataSetElement the metadata set element for this metadata. 603 * @param rawValues the raw values of the form 604 * @param rawComments the raw comments of the form 605 * @param parentMetadataDefinition the metadata definition. 606 * @param parentMetadataPath the metadata path. 607 * @param externalAndLocalMetadata The paths of externalizable metadata 608 * @throws WorkflowException if an error occurs. 609 */ 610 protected void _bindAndValidateMetadataSetElement(Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, List<Map<String, String>>> rawComments, MetadataDefinition parentMetadataDefinition, String parentMetadataPath, Set<String> externalAndLocalMetadata) throws WorkflowException 611 { 612 for (AbstractMetadataSetElement subElement : metadataSetElement.getElements()) 613 { 614 if (subElement instanceof MetadataDefinitionReference) 615 { 616 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subElement; 617 MetadataDefinition metadataDefinition = _getMetadataDefinition(content, parentMetadataDefinition, metadataDefRef.getMetadataName()); 618 619 if (metadataDefinition == null) 620 { 621 throw new IllegalArgumentException("Unable to get the metadata definition of metadata \"" + parentMetadataPath + ContentConstants.METADATA_PATH_SEPARATOR + metadataDefRef.getMetadataName() + "\""); 622 } 623 624 if (_contentTypesHelper.canWrite(content, metadataDefinition)) 625 { 626 String subMetadataPath = parentMetadataPath + metadataDefinition.getName(); 627 _bindAndValidateMetadata(content, form, allErrors, subElement, rawValues, rawComments, metadataDefinition, subMetadataPath, externalAndLocalMetadata); 628 _bindComments(rawComments, form, metadataDefinition, subMetadataPath); // Handle metadata comments 629 } 630 } 631 else 632 { 633 _bindAndValidateMetadataSetElement(content, form, allErrors, subElement, rawValues, rawComments, parentMetadataDefinition, parentMetadataPath, externalAndLocalMetadata); 634 } 635 } 636 } 637 638 /** 639 * Validates the form. 640 * @param content the content. 641 * @param form the form. 642 * @param metadataSet the metadata set. 643 * @param allErrors the errors. 644 */ 645 protected void _validateForm(Content content, Form form, MetadataSet metadataSet, AllErrors allErrors) 646 { 647 Errors errors = new Errors(); 648 649 String[] allContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 650 651 for (String cTypeId : allContentTypes) 652 { 653 ContentType contentType = _contentTypeExtensionPoint.getExtension(cTypeId); 654 655 for (ContentValidator validator : contentType.getGlobalValidators()) 656 { 657 validator.validate(content, form, metadataSet, errors); 658 } 659 } 660 661 if (errors.hasErrors()) 662 { 663 // Global error 664 allErrors.addError(GLOBAL_ERROR_KEY, errors); 665 } 666 667 } 668 669 /** 670 * Synchronize a metadata set element with a composite metadata. 671 * @param content the content. 672 * @param metadata the composite metadata to synchronize. 673 * @param form the form. 674 * @param allErrors the errors. 675 * @param user the user. 676 * @param metadataSetElement the metadata set element for this metadata. 677 * @param parentMetadataDefinition the metadata definition. 678 * @param metadataPath the metadata path. 679 * @param invertEditActionId The action id for editing invert relation 680 * @param externalAndLocalMetadata The paths of externalizable metadata 681 * @throws WorkflowException if an error occurs. 682 */ 683 protected void _synchronizeMetadataSetElement(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, MetadataDefinition parentMetadataDefinition, String metadataPath, int invertEditActionId, Set<String> externalAndLocalMetadata) throws WorkflowException 684 { 685 for (AbstractMetadataSetElement subElement : metadataSetElement.getElements()) 686 { 687 if (subElement instanceof MetadataDefinitionReference) 688 { 689 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subElement; 690 MetadataDefinition metadataDefinition = _getMetadataDefinition(content, parentMetadataDefinition, metadataDefRef.getMetadataName()); 691 692 if (_contentTypesHelper.canWrite(content, metadataDefinition)) 693 { 694 String subMetadataPath = metadataPath + metadataDefinition.getName(); 695 _synchronizeMetadata(content, metadata, form, allErrors, user, subElement, metadataDefinition, subMetadataPath, invertEditActionId, externalAndLocalMetadata); 696 } 697 } 698 else 699 { 700 _synchronizeMetadataSetElement(content, metadata, form, allErrors, user, subElement, parentMetadataDefinition, metadataPath, invertEditActionId, externalAndLocalMetadata); 701 } 702 } 703 } 704 705 /** 706 * Synchronize to synchronize a metadata set element with a composite metadata. 707 * @param content the content. 708 * @param metadata the composite metadata to synchronize. 709 * @param form the form. 710 * @param allErrors the errors. 711 * @param user the user. 712 * @param metadataSetElement the metadata set element for this metadata. 713 * @param parentMetadataDefinition the metadata definition. 714 * @param metadataPath the metadata path. 715 * @param invertEditActionId The action id for editing invert relation 716 * @throws WorkflowException if an error occurs. 717 * @throws AmetysRepositoryException if an error occurred 718 */ 719 protected void _prepareSynchronizeMetadataSetElement(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, MetadataDefinition parentMetadataDefinition, String metadataPath, int invertEditActionId) throws WorkflowException, AmetysRepositoryException 720 { 721 for (AbstractMetadataSetElement subElement : metadataSetElement.getElements()) 722 { 723 if (subElement instanceof MetadataDefinitionReference) 724 { 725 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subElement; 726 MetadataDefinition metadataDefinition = _getMetadataDefinition(content, parentMetadataDefinition, metadataDefRef.getMetadataName()); 727 728 if (_contentTypesHelper.canWrite(content, metadataDefinition)) 729 { 730 String subMetadataPath = metadataPath + metadataDefinition.getName(); 731 _prepareSynchronizeMetadata(content, metadata, form, allErrors, user, subElement, metadataDefinition, subMetadataPath, invertEditActionId); 732 } 733 } 734 else 735 { 736 _prepareSynchronizeMetadataSetElement(content, metadata, form, allErrors, user, subElement, parentMetadataDefinition, metadataPath, invertEditActionId); 737 } 738 } 739 } 740 741 742 743 /** 744 * Updates common metadata (last contributor, last modification date, ...). 745 * @param content the content. 746 * @param user the user. 747 * @throws WorkflowException if an error occurs. 748 */ 749 protected void _updateCommonMetadata(ModifiableContent content, UserIdentity user) throws WorkflowException 750 { 751 if (user != null) 752 { 753 content.setLastContributor(user); 754 } 755 content.setLastModified(new Date()); 756 757 if (content instanceof WorkflowAwareContent) 758 { 759 // Remove the proposal date. 760 ((WorkflowAwareContent) content).setProposalDate(null); 761 } 762 } 763 764 /** 765 * Retrieves a sub metadata definition from a content type or 766 * a parent metadata definition. 767 * @param content the content. 768 * @param parentMetadataDefinition the parent metadata definition. 769 * @param metadataName the metadata name. 770 * @return the metadata definition found or <code>null</code> otherwise. 771 */ 772 protected MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition parentMetadataDefinition, String metadataName) 773 { 774 if (parentMetadataDefinition == null) 775 { 776 return _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); 777 } 778 else 779 { 780 return parentMetadataDefinition.getMetadataDefinition(metadataName); 781 } 782 } 783 784 /** 785 * Bind and validate a form. 786 * @param content the content. 787 * @param form the form. 788 * @param allErrors the errors. 789 * @param metadataSetElement the metadata set element for this metadata. 790 * @param rawValues the raw values. 791 * @param rawComments the raw comments. 792 * @param metadataDefinition the metadata definition. 793 * @param metadataPath the metadata path. 794 * @param externalAndLocalMetadata The paths of externalizable metadata 795 * @throws WorkflowException if an error occurs. 796 */ 797 protected void _bindAndValidateMetadata(Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, List<Map<String, String>>> rawComments, MetadataDefinition metadataDefinition, String metadataPath, Set<String> externalAndLocalMetadata) throws WorkflowException 798 { 799 String metadataName = metadataDefinition.getName(); 800 MetadataType type = metadataDefinition.getType(); 801 802 if (!_contentTypesHelper.canWrite(content, metadataDefinition)) 803 { 804 throw new WorkflowException("Current user has no right to edit metadata " + metadataName); 805 } 806 807 boolean externalizable = externalAndLocalMetadata.contains(metadataPath); 808 Object rawValue = rawValues.get(FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.')); 809 810 switch (type) 811 { 812 case STRING: 813 _bindAndValidateStringMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 814 break; 815 case MULTILINGUAL_STRING: 816 _bindAndValidateMultilingualStringMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 817 break; 818 case USER: 819 _bindAndValidateUserMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 820 break; 821 case DATE: 822 _bindAndValidateDateMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 823 break; 824 case DATETIME: 825 _bindAndValidateDateTimeMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 826 break; 827 case LONG: 828 _bindAndValidateLongMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 829 break; 830 case GEOCODE: 831 _bindAndValidateGeocodeMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 832 break; 833 case DOUBLE: 834 _bindAndValidateDoubleMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 835 break; 836 case BOOLEAN: 837 _bindAndValidateBooleanMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 838 break; 839 case BINARY: 840 _bindAndValidateBinaryMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 841 break; 842 case FILE: 843 _bindAndValidateFileMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 844 break; 845 case RICH_TEXT: 846 _bindAndValidateRichText(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 847 break; 848 case COMPOSITE: 849 _bindAndValidateCompositeMetadata(allErrors, form, content, metadataName, metadataSetElement, metadataDefinition, metadataPath, rawValue, rawValues, rawComments, externalAndLocalMetadata); 850 break; 851 case REFERENCE: 852 _bindAndValidateReferenceMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 853 break; 854 case CONTENT: 855 _bindAndValidateContentReferenceMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 856 break; 857 case SUB_CONTENT: 858 _bindAndValidateSubContentMetadata(allErrors, form, content, metadataDefinition, metadataPath, rawValue, rawValues, externalizable); 859 break; 860 default: 861 throw new WorkflowException("Unsupported type: " + type); 862 } 863 } 864 865 private String[] _getLocalValues (String rawValue, MetadataDefinition metadataDefinition) 866 { 867 Map<String, Object> externalizableValue = _jsonUtils.convertJsonToMap(rawValue); 868 return _getMetadataValues(externalizableValue.get("local"), metadataDefinition); 869 } 870 871 private String[] _getExternalValues (String rawValue, MetadataDefinition metadataDefinition) 872 { 873 Map<String, Object> externalizableValue = _jsonUtils.convertJsonToMap(rawValue); 874 return _getMetadataValues(externalizableValue.get("external"), metadataDefinition); 875 } 876 877 /** 878 * Get a metadata values from the request. 879 * @param rawValues the raw values. 880 * @param form the form. 881 * @param metadataDefinition the metadata definition. 882 * @param metadataPath the metadata path. 883 * @return the metadata values as a String array. 884 */ 885 protected String[] _getMetadataValues(Map<String, Object> rawValues, Form form, MetadataDefinition metadataDefinition, String metadataPath) 886 { 887 Object rawValue = rawValues.get(FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.')); 888 return _getMetadataValues(rawValue, metadataDefinition); 889 } 890 891 /** 892 * Get a metadata values from raw value 893 * @param rawValue The raw value 894 * @param metadataDefinition the metadata definition. 895 * @return the metadata values as a String array. 896 */ 897 @SuppressWarnings("unchecked") 898 protected String[] _getMetadataValues(Object rawValue, MetadataDefinition metadataDefinition) 899 { 900 List<String> metadataValues = new ArrayList<>(); 901 902 if (rawValue != null) 903 { 904 if (metadataDefinition.isMultiple()) 905 { 906 // The value can either be passed as a List object or as a JSON-encoded string. 907 List<Object> listValue = Collections.emptyList(); 908 if (rawValue instanceof List) 909 { 910 listValue = (List<Object>) rawValue; 911 } 912 else if (rawValue instanceof String) 913 { 914 listValue = _jsonUtils.convertJsonToList((String) rawValue); 915 } 916 917 for (Object valueAsObject : listValue) 918 { 919 if (valueAsObject instanceof String) 920 { 921 metadataValues.add((String) valueAsObject); 922 } 923 else 924 { 925 metadataValues.add(_jsonUtils.convertObjectToJson(valueAsObject)); 926 } 927 } 928 } 929 else 930 { 931 metadataValues.add(String.valueOf(rawValue)); 932 } 933 } 934 935 return metadataValues.toArray(new String[metadataValues.size()]); 936 } 937 938 /** 939 * Bind the comments of a field to the form 940 * @param rawComments The raw comments of the form 941 * @param form The forms 942 * @param metadataDefinition the metadata definition. 943 * @param metadataPath the metadata path. 944 */ 945 protected void _bindComments(Map<String, List<Map<String, String>>> rawComments, Form form, MetadataDefinition metadataDefinition, String metadataPath) 946 { 947 if (rawComments == null) 948 { 949 return; 950 } 951 952 String fieldName = metadataDefinition.getName(); 953 List<Map<String, String>> rawFieldcomments = rawComments.get(FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.')); 954 List<MetadataComment> comments = new ArrayList<>(); 955 956 if (rawFieldcomments != null) 957 { 958 for (Map<String, String> rawEntry : rawFieldcomments) 959 { 960 String text = rawEntry.get("text"); 961 String author = rawEntry.get("author"); 962 Date date = DateUtils.parse(rawEntry.get("date")); 963 comments.add(new MetadataComment(text, date, author)); 964 } 965 } 966 967 form.setCommentsField(fieldName, comments.toArray(new MetadataComment[comments.size()])); 968 } 969 970 /** 971 * Bind and validate a composite metadata. 972 * @param allErrors for storing validation errors. 973 * @param form the form. 974 * @param metadataDefinition the metadata definition. 975 * @param content the current content 976 * @param metadataName the metadata name 977 * @param metadataSetElement the metadata set element 978 * @param metadataPath the metadata path from the content. 979 * @param rawValue the submitted values. 980 * @param rawValues The raw values of the form 981 * @param rawComments The raw comments of the form 982 * @param externalAndLocalMetadata The paths of externalizable metadata 983 * @throws AmetysRepositoryException if an error occurs. 984 * @throws WorkflowException if an error occurs. 985 */ 986 protected void _bindAndValidateCompositeMetadata(AllErrors allErrors, Form form, Content content, String metadataName, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, Map<String, List<Map<String, String>>> rawComments, Set<String> externalAndLocalMetadata) throws AmetysRepositoryException, WorkflowException 987 { 988 if (metadataDefinition instanceof RepeaterDefinition) 989 { 990 _bindAndValidateRepeater(content, form, allErrors, metadataSetElement, rawValues, rawComments, (RepeaterDefinition) metadataDefinition, metadataPath, externalAndLocalMetadata); 991 } 992 else 993 { 994 String key = FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 995 996 Form compositeForm = null; 997 if (!rawValues.containsKey(key) || rawValues.get(key) != null) 998 { 999 compositeForm = new Form(); 1000 _bindAndValidateMetadataSetElement(content, compositeForm, allErrors, metadataSetElement, rawValues, rawComments, metadataDefinition, metadataPath + "/", externalAndLocalMetadata); 1001 } 1002 form.setCompositeField(metadataName, compositeForm); 1003 } 1004 } 1005 1006 /** 1007 * Bind and validate a repeater. 1008 * @param content the content. 1009 * @param form the form. 1010 * @param allErrors the errors. 1011 * @param metadataSetElement the metadata set element for this metadata. 1012 * @param rawValues the raw values of the form 1013 * @param rawComments the raw comments of the form 1014 * @param repeaterDefinition the repeater definition. 1015 * @param metadataPath the metadata path. 1016 * @param externalAndLocalMetadata The paths of externalizable metadata 1017 * @throws WorkflowException if an error occurs. 1018 */ 1019 protected void _bindAndValidateRepeater(Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, List<Map<String, String>>> rawComments, RepeaterDefinition repeaterDefinition, String metadataPath, Set<String> externalAndLocalMetadata) throws WorkflowException 1020 { 1021 String metadataName = repeaterDefinition.getName(); 1022 int repeaterSizeValue; 1023 String repeaterParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1024 1025 String repeaterSize = (String) rawValues.get(repeaterParamsPrefix + ".size"); 1026 if (repeaterSize == null) 1027 { 1028 throw new WorkflowException("Missing request parameter size for metadata: " + metadataPath); 1029 } 1030 try 1031 { 1032 repeaterSizeValue = Integer.valueOf(repeaterSize); 1033 } 1034 catch (NumberFormatException e) 1035 { 1036 throw new WorkflowException("Invalid size: " + repeaterSize, e); 1037 } 1038 1039 RepeaterField repeaterField = new RepeaterField(); 1040 1041 String repeaterMode = (String) rawValues.get(repeaterParamsPrefix + ".mode"); 1042 repeaterField.setMode(repeaterMode); 1043 1044 for (int i = 1; i <= repeaterSizeValue; i++) 1045 { 1046 RepeaterEntry repeaterEntry = new RepeaterEntry(); 1047 1048 1049 int previousPositionValue = -1; 1050 String previousPosition = (String) rawValues.get(repeaterParamsPrefix + "." + i + ".previous-position"); 1051 if (previousPosition != null) 1052 { 1053 try 1054 { 1055 previousPositionValue = Integer.valueOf(previousPosition); 1056 repeaterEntry.setPreviousPosition(previousPositionValue); 1057 } 1058 catch (NumberFormatException e) 1059 { 1060 throw new WorkflowException("Invalid position: " + previousPosition, e); 1061 } 1062 } 1063 1064 int positionValue = i; 1065 String position = (String) rawValues.get(repeaterParamsPrefix + "." + i + ".position"); 1066 if (position != null) 1067 { 1068 try 1069 { 1070 positionValue = Integer.valueOf(position); 1071 } 1072 catch (NumberFormatException e) 1073 { 1074 throw new WorkflowException("Invalid position: " + position, e); 1075 } 1076 } 1077 repeaterEntry.setPosition(positionValue); 1078 1079 // Bind and validate each entry 1080 _bindAndValidateMetadataSetElement(content, repeaterEntry, allErrors, metadataSetElement, rawValues, rawComments, repeaterDefinition, metadataPath + "/" + i + "/", externalAndLocalMetadata); 1081 1082 repeaterField.addEntry(repeaterEntry); 1083 } 1084 1085 // Size will be checked later 1086 1087 form.setField(metadataName, repeaterField); 1088 } 1089 1090 /** 1091 * Bind and validate a string metadata. 1092 * @param allErrors for storing validation errors. 1093 * @param form the form. 1094 * @param metadataDefinition the metadata definition. 1095 * @param content the content 1096 * @param metadataPath the metadata path from the content. 1097 * @param rawValue the submitted value. 1098 * @param rawValues the raw values of the form 1099 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1100 * @throws AmetysRepositoryException if an error occurs. 1101 * @throws WorkflowException if an error occurs. 1102 */ 1103 @SuppressWarnings("unchecked") 1104 protected void _bindAndValidateStringMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1105 { 1106 String metadataName = metadataDefinition.getName(); 1107 1108 AbstractField field = null; 1109 1110 if (externalizable) 1111 { 1112 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1113 SimpleField<String> localField = new SimpleField<>(localValues); 1114 1115 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1116 SimpleField<String> extField = new SimpleField<>(extValues); 1117 1118 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1119 } 1120 else 1121 { 1122 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1123 field = new SimpleField<>(metadataValues); 1124 } 1125 1126 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1127 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1128 field.setMode(metadataMode); 1129 1130 String[] values = field instanceof ExternalizableField ? ((SimpleField<String>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<String>) field).getValues(); 1131 1132 String[] valuesToValidate = values; 1133 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 1134 { 1135 // Validate external values 1136 valuesToValidate = ((SimpleField<String>) ((ExternalizableField) field).getExternalField()).getValues(); 1137 } 1138 1139 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 1140 { 1141 if (metadataDefinition.isMultiple()) 1142 { 1143 form.setField(metadataName, field); 1144 } 1145 else 1146 { 1147 if (externalizable || (values.length > 0 && !values[0].equals(""))) 1148 { 1149 form.setField(metadataName, field); 1150 } 1151 } 1152 } 1153 } 1154 1155 /** 1156 * Bind and validate a multilingual string metadata. 1157 * @param allErrors for storing validation errors. 1158 * @param form the form. 1159 * @param metadataDefinition the metadata definition. 1160 * @param content the content 1161 * @param metadataPath the metadata path from the content. 1162 * @param rawValue the submitted value. 1163 * @param rawValues the raw values of the form 1164 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1165 * @throws AmetysRepositoryException if an error occurs. 1166 * @throws WorkflowException if an error occurs. 1167 */ 1168 @SuppressWarnings("unchecked") 1169 protected void _bindAndValidateMultilingualStringMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1170 { 1171 String metadataName = metadataDefinition.getName(); 1172 1173 AbstractField field = null; 1174 1175 if (externalizable) 1176 { 1177 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1178 SimpleField<MultilingualString> localField = _bindMultilingualField(localValues); 1179 1180 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1181 SimpleField<MultilingualString> extField = _bindMultilingualField(extValues); 1182 1183 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1184 } 1185 else 1186 { 1187 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1188 field = _bindMultilingualField(metadataValues); 1189 } 1190 1191 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1192 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1193 field.setMode(metadataMode); 1194 1195 MultilingualString[] values = field instanceof ExternalizableField ? ((SimpleField<MultilingualString>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<MultilingualString>) field).getValues(); 1196 1197 MultilingualString[] valuesToValidate = values; 1198 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 1199 { 1200 // Validate external values 1201 valuesToValidate = ((SimpleField<MultilingualString>) ((ExternalizableField) field).getExternalField()).getValues(); 1202 } 1203 1204 String[] strValuesToValidate = new String[0]; 1205 if (valuesToValidate.length > 0) 1206 { 1207 List<String> strValues = valuesToValidate[0].getValues(); 1208 strValuesToValidate = valuesToValidate[0].getValues().toArray(new String[strValues.size()]); 1209 } 1210 1211 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, strValuesToValidate)) 1212 { 1213 if (externalizable || (strValuesToValidate.length > 0 && !strValuesToValidate[0].equals(""))) 1214 { 1215 form.setField(metadataName, field); 1216 } 1217 } 1218 } 1219 1220 /** 1221 * Bind a multilingual field from form values 1222 * @param values the form values 1223 * @return The multilingual field 1224 */ 1225 protected SimpleField<MultilingualString> _bindMultilingualField (String[] values) 1226 { 1227 if (values.length > 0 && !values[0].equals("")) 1228 { 1229 Map<String, Object> localesValues = _jsonUtils.convertJsonToMap(values[0]); 1230 1231 MultilingualString multilingualString = new MultilingualString(); 1232 for (Entry<String, Object> entry : localesValues.entrySet()) 1233 { 1234 Object value = entry.getValue(); 1235 if (value != null && StringUtils.isNotEmpty((String) value)) 1236 { 1237 multilingualString.add(LocaleUtils.toLocale(entry.getKey()), (String) value); 1238 } 1239 } 1240 1241 return new SimpleField<>(new MultilingualString[] {multilingualString}); 1242 } 1243 1244 return new SimpleField<>(new MultilingualString[0]); 1245 } 1246 1247 /** 1248 * Bind and validate a user metadata. 1249 * @param allErrors for storing validation errors. 1250 * @param form the form. 1251 * @param content the content 1252 * @param metadataDefinition the metadata definition. 1253 * @param metadataPath the metadata path from the content. 1254 * @param rawValue the submitted value. 1255 * @param rawValues the raw values of the form 1256 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1257 * @throws AmetysRepositoryException if an error occurs. 1258 * @throws WorkflowException if an error occurs. 1259 */ 1260 protected void _bindAndValidateUserMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1261 { 1262 String metadataName = metadataDefinition.getName(); 1263 1264 AbstractField field = null; 1265 if (externalizable) 1266 { 1267 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1268 SimpleField<UserIdentity> localField = _bindUserField(localValues); 1269 1270 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1271 SimpleField<UserIdentity> extField = _bindUserField(extValues); 1272 1273 if (localField != null) 1274 { 1275 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1276 } 1277 } 1278 else 1279 { 1280 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1281 field = _bindUserField(metadataValues); 1282 } 1283 1284 if (field != null) 1285 { 1286 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replaceAll("/", "."); 1287 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1288 field.setMode(metadataMode); 1289 form.setField(metadataName, field); 1290 } 1291 } 1292 1293 /** 1294 * Bind a user field from form values 1295 * @param values the form values 1296 * @return The user field 1297 */ 1298 protected SimpleField<UserIdentity> _bindUserField (String[] values) 1299 { 1300 List<UserIdentity> users = new ArrayList<>(); 1301 for (String value : values) 1302 { 1303 Map<String, Object> userValue = _jsonUtils.convertJsonToMap(value); 1304 1305 UserIdentity userIdentity = _userHelper.json2userIdentity(userValue); 1306 if (userIdentity != null) 1307 { 1308 users.add(userIdentity); 1309 } 1310 } 1311 1312 return new SimpleField<>(users.toArray(new UserIdentity[users.size()])); 1313 } 1314 1315 /** 1316 * Bind and validate a date metadata. 1317 * @param allErrors for storing validation errors. 1318 * @param form the form. 1319 * @param content the content 1320 * @param metadataDefinition the metadata definition. 1321 * @param metadataPath the metadata path from the content. 1322 * @param rawValue the submitted value. 1323 * @param rawValues the raw values of the form 1324 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1325 * @throws AmetysRepositoryException if an error occurs. 1326 * @throws WorkflowException if an error occurs. 1327 */ 1328 @SuppressWarnings("unchecked") 1329 protected void _bindAndValidateDateMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1330 { 1331 String metadataName = metadataDefinition.getName(); 1332 1333 AbstractField field = null; 1334 if (externalizable) 1335 { 1336 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1337 SimpleField<Date> localField = _bindDateField(localValues, metadataPath, allErrors); 1338 1339 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1340 SimpleField<Date> extField = _bindDateField(extValues, metadataPath, allErrors); 1341 1342 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1343 } 1344 else 1345 { 1346 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1347 field = _bindDateField(metadataValues, metadataPath, allErrors); 1348 } 1349 1350 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1351 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1352 field.setMode(metadataMode); 1353 1354 Date[] values = field instanceof ExternalizableField ? ((SimpleField<Date>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<Date>) field).getValues(); 1355 1356 Date[] valuesToValidate = values; 1357 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 1358 { 1359 // Validate external values 1360 valuesToValidate = ((SimpleField<Date>) ((ExternalizableField) field).getExternalField()).getValues(); 1361 } 1362 1363 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 1364 { 1365 if (metadataDefinition.isMultiple()) 1366 { 1367 form.setField(metadataName, field); 1368 } 1369 else 1370 { 1371 if (externalizable || values.length > 0) 1372 { 1373 form.setField(metadataName, field); 1374 } 1375 } 1376 } 1377 } 1378 1379 /** 1380 * Bind a date field from form values 1381 * @param values the form values 1382 * @param metadataPath The path of metadata 1383 * @param allErrors for storing validation errors. 1384 * @return The date field 1385 */ 1386 protected SimpleField<Date> _bindDateField (String[] values, String metadataPath, AllErrors allErrors) 1387 { 1388 List<Date> dateValues = new ArrayList<>(); 1389 1390 for (int i = 0; i < values.length; i++) 1391 { 1392 if (!"".equals(values[i])) 1393 { 1394 try 1395 { 1396 LocalDate ld = LocalDate.parse(values[i], DateTimeFormatter.ISO_DATE_TIME); 1397 dateValues.add(DateUtils.asDate(ld)); 1398 } 1399 catch (DateTimeParseException e) 1400 { 1401 Errors parseErrors = new Errors(); 1402 1403 List<String> parameters = new ArrayList<>(); 1404 parameters.add(values[i]); 1405 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_DATE_INVALID", parameters)); 1406 allErrors.addError(metadataPath, parseErrors); 1407 } 1408 } 1409 } 1410 1411 Date[] dateArray = dateValues.toArray(new Date[dateValues.size()]); 1412 return new SimpleField<>(dateArray); 1413 } 1414 1415 /** 1416 * Bind and validate a date time metadata. 1417 * @param allErrors for storing validation errors. 1418 * @param form the form. 1419 * @param content the content 1420 * @param metadataDefinition the metadata definition. 1421 * @param metadataPath the metadata path from the content. 1422 * @param rawValue the submitted value. 1423 * @param rawValues the raw values of the form 1424 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1425 * @throws AmetysRepositoryException if an error occurs. 1426 * @throws WorkflowException if an error occurs. 1427 */ 1428 @SuppressWarnings("unchecked") 1429 protected void _bindAndValidateDateTimeMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1430 { 1431 String metadataName = metadataDefinition.getName(); 1432 1433 AbstractField field = null; 1434 if (externalizable) 1435 { 1436 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1437 SimpleField<Date> localField = _bindDateTimeField(localValues, metadataPath, allErrors); 1438 1439 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1440 SimpleField<Date> extField = _bindDateTimeField(extValues, metadataPath, allErrors); 1441 1442 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1443 } 1444 else 1445 { 1446 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1447 field = _bindDateTimeField(metadataValues, metadataPath, allErrors); 1448 } 1449 1450 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1451 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1452 field.setMode(metadataMode); 1453 1454 Date[] values = field instanceof ExternalizableField ? ((SimpleField<Date>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<Date>) field).getValues(); 1455 1456 Date[] valuesToValidate = values; 1457 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 1458 { 1459 // Validate external values 1460 valuesToValidate = ((SimpleField<Date>) ((ExternalizableField) field).getExternalField()).getValues(); 1461 } 1462 1463 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 1464 { 1465 if (metadataDefinition.isMultiple()) 1466 { 1467 form.setField(metadataName, field); 1468 } 1469 else 1470 { 1471 if (externalizable || values.length > 0) 1472 { 1473 form.setField(metadataName, field); 1474 } 1475 } 1476 } 1477 } 1478 1479 /** 1480 * Bind a date time field from form values 1481 * @param values the form values 1482 * @param metadataPath The path of metadata 1483 * @param allErrors for storing validation errors. 1484 * @return The date field 1485 */ 1486 protected SimpleField<Date> _bindDateTimeField (String[] values, String metadataPath, AllErrors allErrors) 1487 { 1488 List<Date> dateValues = new ArrayList<>(); 1489 1490 for (int i = 0; i < values.length; i++) 1491 { 1492 if (!"".equals(values[i])) 1493 { 1494 try 1495 { 1496 LocalDateTime ld = LocalDateTime.parse(values[i], DateTimeFormatter.ISO_DATE_TIME); 1497 dateValues.add(DateUtils.asDate(ld)); 1498 } 1499 catch (DateTimeParseException e) 1500 { 1501 Errors parseErrors = new Errors(); 1502 1503 List<String> parameters = new ArrayList<>(); 1504 parameters.add(values[i]); 1505 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_DATETIME_INVALID", parameters)); 1506 allErrors.addError(metadataPath, parseErrors); 1507 } 1508 } 1509 } 1510 1511 Date[] dateArray = dateValues.toArray(new Date[dateValues.size()]); 1512 return new SimpleField<>(dateArray); 1513 } 1514 1515 /** 1516 * Bind and validate a long metadata. 1517 * @param allErrors for storing validation errors. 1518 * @param form the form. 1519 * @param content the content 1520 * @param metadataDefinition the metadata definition. 1521 * @param metadataPath the metadata path from the content. 1522 * @param rawValue the submitted value. 1523 * @param rawValues the raw values of the form 1524 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1525 * @throws AmetysRepositoryException if an error occurs. 1526 * @throws WorkflowException if an error occurs. 1527 */ 1528 @SuppressWarnings("unchecked") 1529 protected void _bindAndValidateLongMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1530 { 1531 String metadataName = metadataDefinition.getName(); 1532 1533 AbstractField field = null; 1534 if (externalizable) 1535 { 1536 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1537 SimpleField<Long> localField = _bindLongField(localValues, metadataPath, allErrors); 1538 1539 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1540 SimpleField<Long> extField = _bindLongField(extValues, metadataPath, allErrors); 1541 1542 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1543 } 1544 else 1545 { 1546 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1547 field = _bindLongField(metadataValues, metadataPath, allErrors); 1548 } 1549 1550 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1551 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1552 field.setMode(metadataMode); 1553 1554 Long[] values = field instanceof ExternalizableField ? ((SimpleField<Long>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<Long>) field).getValues(); 1555 1556 Long[] valuesToValidate = values; 1557 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 1558 { 1559 // Validate external values 1560 valuesToValidate = ((SimpleField<Long>) ((ExternalizableField) field).getExternalField()).getValues(); 1561 } 1562 1563 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 1564 { 1565 if (metadataDefinition.isMultiple()) 1566 { 1567 form.setField(metadataName, field); 1568 } 1569 else 1570 { 1571 if (externalizable || values.length > 0) 1572 { 1573 form.setField(metadataName, field); 1574 } 1575 } 1576 } 1577 } 1578 1579 /** 1580 * Bind a long field from form values 1581 * @param values the form values 1582 * @param metadataPath The path of metadata 1583 * @param allErrors for storing validation errors. 1584 * @return The long field 1585 */ 1586 protected SimpleField<Long> _bindLongField (String[] values, String metadataPath, AllErrors allErrors) 1587 { 1588 List<Long> longValues = new ArrayList<>(); 1589 1590 for (int i = 0; i < values.length; i++) 1591 { 1592 if (!"".equals(values[i])) 1593 { 1594 try 1595 { 1596 longValues.add(Long.parseLong(values[i])); 1597 } 1598 catch (NumberFormatException e) 1599 { 1600 Errors parseErrors = new Errors(); 1601 1602 List<String> parameters = new ArrayList<>(); 1603 parameters.add(values[i]); 1604 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_LONG_INVALID", parameters)); 1605 allErrors.addError(metadataPath, parseErrors); 1606 } 1607 } 1608 } 1609 1610 Long[] longArray = longValues.toArray(new Long[longValues.size()]); 1611 return new SimpleField<>(longArray); 1612 } 1613 1614 /** 1615 * Bind and validate a geocode metadata. 1616 * @param allErrors for storing validation errors. 1617 * @param form the form. 1618 * @param content the content 1619 * @param metadataDefinition the metadata definition. 1620 * @param metadataPath the metadata path from the content. 1621 * @param rawValue the submitted value. 1622 * @param rawValues the raw values of the form 1623 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1624 * @throws AmetysRepositoryException if an error occurs. 1625 * @throws WorkflowException if an error occurs. 1626 */ 1627 protected void _bindAndValidateGeocodeMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1628 { 1629 String metadataName = metadataDefinition.getName(); 1630 1631 AbstractField field = null; 1632 if (externalizable) 1633 { 1634 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1635 SimpleField<Double> localField = _bindGeoCodeField(localValues); 1636 1637 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1638 SimpleField<Double> extField = _bindGeoCodeField(extValues); 1639 1640 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1641 } 1642 else 1643 { 1644 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1645 field = _bindGeoCodeField(metadataValues); 1646 } 1647 1648 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1649 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1650 1651 // TODO Validate metadata. Need a DoubleValidator ?? 1652 1653 if (field != null) 1654 { 1655 field.setMode(metadataMode); 1656 form.setField(metadataName, field); 1657 } 1658 } 1659 1660 /** 1661 * Bind a geocode field from form values 1662 * @param values the form values 1663 * @return The geocode field 1664 */ 1665 protected SimpleField<Double> _bindGeoCodeField (String[] values) 1666 { 1667 if (values.length > 0) 1668 { 1669 Map<String, Object> geocodeValues = _jsonUtils.convertJsonToMap(values[0]); 1670 1671 Double longitude = (Double) geocodeValues.get("longitude"); 1672 Double latitude = (Double) geocodeValues.get("latitude"); 1673 1674 if (longitude != null && latitude != null) 1675 { 1676 return new SimpleField<>(new Double[]{longitude, latitude}); 1677 } 1678 } 1679 return null; 1680 } 1681 1682 /** 1683 * Bind and validate a double metadata. 1684 * @param allErrors for storing validation errors. 1685 * @param form the form. 1686 * @param content the content 1687 * @param metadataDefinition the metadata definition. 1688 * @param metadataPath the metadata path from the content. 1689 * @param rawValue the submitted value. 1690 * @param rawValues the raw values of the form 1691 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1692 * @throws AmetysRepositoryException if an error occurs. 1693 * @throws WorkflowException if an error occurs. 1694 */ 1695 @SuppressWarnings("unchecked") 1696 protected void _bindAndValidateDoubleMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1697 { 1698 String metadataName = metadataDefinition.getName(); 1699 1700 AbstractField field = null; 1701 if (externalizable) 1702 { 1703 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1704 SimpleField<Double> localField = _bindDoubleField(localValues, metadataPath, allErrors); 1705 1706 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1707 SimpleField<Double> extField = _bindDoubleField(extValues, metadataPath, allErrors); 1708 1709 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1710 } 1711 else 1712 { 1713 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1714 field = _bindDoubleField(metadataValues, metadataPath, allErrors); 1715 } 1716 1717 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1718 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1719 field.setMode(metadataMode); 1720 1721 Double[] values = field instanceof ExternalizableField ? ((SimpleField<Double>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<Double>) field).getValues(); 1722 1723 Double[] valuesToValidate = values; 1724 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 1725 { 1726 // Validate external values 1727 valuesToValidate = ((SimpleField<Double>) ((ExternalizableField) field).getExternalField()).getValues(); 1728 } 1729 1730 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 1731 { 1732 if (metadataDefinition.isMultiple()) 1733 { 1734 form.setField(metadataName, field); 1735 } 1736 else 1737 { 1738 if (externalizable || values.length != 0) 1739 { 1740 form.setField(metadataName, field); 1741 } 1742 } 1743 } 1744 } 1745 1746 /** 1747 * Bind a double field from form values 1748 * @param values the form values 1749 * @param metadataPath The path of metadata 1750 * @param allErrors for storing validation errors. 1751 * @return The double field 1752 */ 1753 protected SimpleField<Double> _bindDoubleField (String[] values, String metadataPath, AllErrors allErrors) 1754 { 1755 List<Double> doubleValues = new ArrayList<>(); 1756 1757 for (int i = 0; i < values.length; i++) 1758 { 1759 if (!"".equals(values[i])) 1760 { 1761 try 1762 { 1763 doubleValues.add(Double.parseDouble(values[i])); 1764 } 1765 catch (NumberFormatException e) 1766 { 1767 Errors parseErrors = new Errors(); 1768 1769 List<String> parameters = new ArrayList<>(); 1770 parameters.add(values[i]); 1771 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_DOUBLE_INVALID", parameters)); 1772 allErrors.addError(metadataPath, parseErrors); 1773 } 1774 } 1775 } 1776 1777 Double[] doubleArray = doubleValues.toArray(new Double[doubleValues.size()]); 1778 return new SimpleField<>(doubleArray); 1779 } 1780 1781 /** 1782 * Bind and validate a boolean metadata. 1783 * @param allErrors for storing validation errors. 1784 * @param form the form. 1785 * @param content the content 1786 * @param metadataDefinition the metadata definition. 1787 * @param metadataPath the metadata path from the content. 1788 * @param rawValue the submitted value. 1789 * @param rawValues the raw values of the form 1790 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1791 * @throws AmetysRepositoryException if an error occurs. 1792 * @throws WorkflowException if an error occurs. 1793 */ 1794 @SuppressWarnings("unchecked") 1795 protected void _bindAndValidateBooleanMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1796 { 1797 String metadataName = metadataDefinition.getName(); 1798 1799 AbstractField field = null; 1800 if (externalizable) 1801 { 1802 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 1803 SimpleField<Boolean> localField = _bindBooleanField(localValues, metadataPath, allErrors); 1804 1805 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1806 SimpleField<Boolean> extField = _bindBooleanField(extValues, metadataPath, allErrors); 1807 1808 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1809 } 1810 else 1811 { 1812 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 1813 field = _bindBooleanField(metadataValues, metadataPath, allErrors); 1814 } 1815 1816 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1817 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1818 field.setMode(metadataMode); 1819 1820 Boolean[] values = field instanceof ExternalizableField ? ((SimpleField<Boolean>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<Boolean>) field).getValues(); 1821 1822 Boolean[] valuesToValidate = values; 1823 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 1824 { 1825 // Validate external values 1826 valuesToValidate = ((SimpleField<Boolean>) ((ExternalizableField) field).getExternalField()).getValues(); 1827 } 1828 1829 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 1830 { 1831 if (metadataDefinition.isMultiple()) 1832 { 1833 form.setField(metadataName, field); 1834 } 1835 else 1836 { 1837 if (values.length != 0) 1838 { 1839 form.setField(metadataName, field); 1840 } 1841 } 1842 } 1843 } 1844 1845 /** 1846 * Bind a boolean field from form values 1847 * @param values the form values 1848 * @param metadataPath The path of metadata 1849 * @param allErrors for storing validation errors. 1850 * @return The boolean field 1851 */ 1852 protected SimpleField<Boolean> _bindBooleanField (String[] values, String metadataPath, AllErrors allErrors) 1853 { 1854 List<Boolean> booleanValues = new ArrayList<>(); 1855 1856 for (int i = 0; i < values.length; i++) 1857 { 1858 try 1859 { 1860 booleanValues.add(Boolean.parseBoolean(values[i])); 1861 } 1862 catch (NumberFormatException e) 1863 { 1864 Errors parseErrors = new Errors(); 1865 1866 List<String> parameters = new ArrayList<>(); 1867 parameters.add(values[i]); 1868 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_BOOLEAN_INVALID", parameters)); 1869 allErrors.addError(metadataPath, parseErrors); 1870 } 1871 } 1872 1873 Boolean[] boolArray = booleanValues.toArray(new Boolean[booleanValues.size()]); 1874 return new SimpleField<>(boolArray); 1875 } 1876 1877 /** 1878 * Bind and validate a binary metadata. 1879 * @param allErrors for storing validation errors. 1880 * @param form the form. 1881 * @param content the content 1882 * @param metadataDefinition the metadata definition. 1883 * @param metadataPath the metadata path from the content. 1884 * @param rawValue the submitted value. 1885 * @param rawValues the raw values of the form 1886 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1887 * @throws AmetysRepositoryException if an error occurs. 1888 * @throws WorkflowException if an error occurs. 1889 */ 1890 protected void _bindAndValidateBinaryMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1891 { 1892 String metadataName = metadataDefinition.getName(); 1893 // FIXME metadataPath is the right metadata path ? 1894 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1895 1896 String[] realValues = new String[0]; 1897 String[] valuesToValidate = new String[0]; 1898 1899 AbstractField field = null; 1900 if (externalizable) 1901 { 1902 realValues = _getLocalValues((String) rawValue, metadataDefinition); 1903 BinaryField localField = _bindBinaryField(realValues, metadataParamsPrefix, rawValues); 1904 1905 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 1906 BinaryField extField = _bindBinaryField(extValues, metadataParamsPrefix, rawValues); 1907 1908 ExternalizableMetadataStatus status = _getExternalizableStatus((String) rawValue); 1909 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 1910 1911 valuesToValidate = status == ExternalizableMetadataStatus.EXTERNAL ? extValues : realValues; 1912 } 1913 else 1914 { 1915 realValues = _getMetadataValues(rawValue, metadataDefinition); 1916 valuesToValidate = realValues; 1917 field = _bindBinaryField(realValues, metadataParamsPrefix, rawValues); 1918 } 1919 1920 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1921 field.setMode(metadataMode); 1922 1923 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 1924 { 1925 if (externalizable || (realValues.length > 0 && !realValues[0].equals(""))) 1926 { 1927 form.setField(metadataName, field); 1928 } 1929 } 1930 } 1931 1932 /** 1933 * Bind a binary field from form values 1934 * @param values the form values 1935 * @param metadataParamsPrefix the prefix for metadata 1936 * @param rawValues The raw values 1937 * @return The binary field 1938 */ 1939 protected BinaryField _bindBinaryField (String[] values, String metadataParamsPrefix, Map<String, Object> rawValues) 1940 { 1941 BinaryField field = new BinaryField(values); 1942 1943 BinaryMetadata binaryValue = (BinaryMetadata) rawValues.get(metadataParamsPrefix + ".rawValue"); 1944 if (binaryValue != null) 1945 { 1946 field.setBinaryValue(binaryValue); 1947 } 1948 1949 return field; 1950 } 1951 1952 /** 1953 * Bind and validate a file metadata. 1954 * @param allErrors for storing validation errors. 1955 * @param form the form. 1956 * @param content the content 1957 * @param metadataDefinition the metadata definition. 1958 * @param metadataPath the metadata path from the content. 1959 * @param rawValue the submitted value. 1960 * @param rawValues the raw values of the form 1961 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 1962 * @throws AmetysRepositoryException if an error occurs. 1963 * @throws WorkflowException if an error occurs. 1964 */ 1965 protected void _bindAndValidateFileMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 1966 { 1967 String metadataName = metadataDefinition.getName(); 1968 // FIXME metadataPath is the right metadata path ? 1969 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 1970 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 1971 1972 String[] realValues = new String[0]; 1973 String[] valuesToValidate = new String[0]; 1974 1975 SimpleField<String> typeField = null; 1976 1977 AbstractField field = null; 1978 if (externalizable) 1979 { 1980 realValues = _getLocalValues((String) rawValue, metadataDefinition); 1981 1982 Map<String, Object> fileValues = _jsonUtils.convertJsonToMap(realValues[0]); 1983 String fileType = (String) fileValues.get("type"); 1984 1985 AbstractField localField; 1986 if (EXPLORER_FILE.equals(fileType)) 1987 { 1988 String fileId = (String) fileValues.get("id"); 1989 localField = new SimpleField<>(new String[]{fileId}); 1990 typeField = new SimpleField<>(new String[]{EXPLORER_FILE}); 1991 } 1992 else if (METADATA_FILE.equals(fileType)) 1993 { 1994 String uploadId = (String) fileValues.get("id"); 1995 localField = _bindBinaryField(new String[]{uploadId}, metadataParamsPrefix, rawValues); 1996 typeField = new SimpleField<>(new String[]{METADATA_FILE}); 1997 } 1998 else 1999 { 2000 localField = new SimpleField<>(null); 2001 } 2002 2003 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 2004 Map<String, Object> extFileValues = _jsonUtils.convertJsonToMap(realValues[0]); 2005 String extFileType = (String) extFileValues.get("type"); 2006 2007 AbstractField extField; 2008 if (EXPLORER_FILE.equals(extFileType)) 2009 { 2010 String fileId = (String) extFileValues.get("id"); 2011 extField = new SimpleField<>(new String[]{fileId}); 2012 } 2013 else if (METADATA_FILE.equals(extFileType)) 2014 { 2015 String uploadId = (String) extFileValues.get("id"); 2016 extField = _bindBinaryField(new String[]{uploadId}, metadataParamsPrefix, rawValues); 2017 } 2018 else 2019 { 2020 extField = new SimpleField<>(null); 2021 } 2022 2023 ExternalizableMetadataStatus status = _getExternalizableStatus((String) rawValue); 2024 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 2025 2026 valuesToValidate = status == ExternalizableMetadataStatus.EXTERNAL ? extValues : realValues; 2027 } 2028 else 2029 { 2030 realValues = _getMetadataValues(rawValue, metadataDefinition); 2031 valuesToValidate = realValues; 2032 2033 if (realValues.length > 0) 2034 { 2035 Map<String, Object> fileValues = _jsonUtils.convertJsonToMap(realValues[0]); 2036 String fileType = (String) fileValues.get("type"); 2037 if (EXPLORER_FILE.equals(fileType)) 2038 { 2039 String fileId = (String) fileValues.get("id"); 2040 field = new SimpleField<>(new String[]{fileId}); 2041 typeField = new SimpleField<>(new String[]{EXPLORER_FILE}); 2042 } 2043 else if (METADATA_FILE.equals(fileType)) 2044 { 2045 String uploadId = (String) fileValues.get("id"); 2046 field = _bindBinaryField(new String[]{uploadId}, metadataParamsPrefix, rawValues); 2047 typeField = new SimpleField<>(new String[]{METADATA_FILE}); 2048 } 2049 else 2050 { 2051 field = new SimpleField<>(null); 2052 } 2053 } 2054 else 2055 { 2056 field = new SimpleField<>(null); 2057 } 2058 } 2059 2060 field.setMode(metadataMode); 2061 2062 if (typeField != null) 2063 { 2064 typeField.setMode(metadataMode); 2065 form.setField(metadataDefinition.getName() + "#type", typeField); 2066 } 2067 2068 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate)) 2069 { 2070 if (externalizable || (realValues.length > 0 && !realValues[0].equals(""))) 2071 { 2072 form.setField(metadataName, field); 2073 } 2074 } 2075 } 2076 2077 /** 2078 * Bind and validate a rich text metadata. 2079 * @param allErrors for storing validation errors. 2080 * @param form the form. 2081 * @param content the content 2082 * @param metadataDefinition the metadata definition. 2083 * @param metadataPath the metadata path from the content. 2084 * @param rawValue the submitted value. 2085 * @param rawValues the raw values of the form 2086 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 2087 * @throws AmetysRepositoryException if an error occurs. 2088 * @throws WorkflowException if an error occurs. 2089 */ 2090 protected void _bindAndValidateRichText(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 2091 { 2092 String metadataName = metadataDefinition.getName(); 2093 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 2094 2095 AbstractField field = null; 2096 if (externalizable) 2097 { 2098 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 2099 RichTextField localField = _bindRichTextField(localValues, metadataParamsPrefix, rawValues); 2100 2101 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 2102 RichTextField extField = _bindRichTextField(extValues, metadataParamsPrefix, rawValues); 2103 2104 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 2105 } 2106 else 2107 { 2108 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 2109 field = _bindRichTextField(metadataValues, metadataParamsPrefix, rawValues); 2110 } 2111 2112 String value = field instanceof ExternalizableField ? ((RichTextField) ((ExternalizableField) field).getLocalField()).getContent() : ((RichTextField) field).getContent(); 2113 2114 String valueToValidate = value; 2115 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 2116 { 2117 // Validate external values 2118 value = ((RichTextField) ((ExternalizableField) field).getExternalField()).getContent(); 2119 } 2120 2121 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valueToValidate)) 2122 { 2123 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 2124 field.setMode(metadataMode); 2125 form.setField(metadataName, field); 2126 } 2127 } 2128 2129 /** 2130 * Bind a richtext field from form values 2131 * @param values the form values 2132 * @param metadataParamsPrefix the prefix for metadata 2133 * @param rawValues The raw values 2134 * @return The richtext field 2135 */ 2136 protected RichTextField _bindRichTextField (String[] values, String metadataParamsPrefix, Map<String, Object> rawValues) 2137 { 2138 if (values.length > 0 && !values[0].equals("")) 2139 { 2140 RichTextField field = new RichTextField(values[0]); 2141 2142 String format = (String) rawValues.get(metadataParamsPrefix + ".format"); 2143 if (StringUtils.isEmpty(format)) 2144 { 2145 format = "html"; 2146 } 2147 field.setFormat(format); 2148 2149 @SuppressWarnings("unchecked") 2150 Map<String, Resource> addData = (Map<String, Resource>) rawValues.get(metadataParamsPrefix + ".additionalData"); 2151 if (addData != null) 2152 { 2153 field.setAdditionalData(addData); 2154 } 2155 2156 return field; 2157 } 2158 return new RichTextField(null); 2159 } 2160 2161 /** 2162 * Bind and validate a reference metadata. 2163 * @param allErrors for storing validation errors. 2164 * @param form the form. 2165 * @param content the content 2166 * @param metadataDefinition the metadata definition. 2167 * @param metadataPath the metadata path from the content. 2168 * @param rawValue the submitted value. 2169 * @param rawValues the raw values of the form 2170 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 2171 * @throws AmetysRepositoryException if an error occurs. 2172 * @throws WorkflowException if an error occurs. 2173 */ 2174 protected void _bindAndValidateReferenceMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 2175 { 2176 String metadataName = metadataDefinition.getName(); 2177 2178 AbstractField field = null; 2179 if (externalizable) 2180 { 2181 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 2182 ReferenceField localField = _bindReferenceField(localValues); 2183 2184 String[] extValues = _getLocalValues((String) rawValue, metadataDefinition); 2185 ReferenceField extField = _bindReferenceField(extValues); 2186 2187 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 2188 } 2189 else 2190 { 2191 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 2192 field = _bindReferenceField(metadataValues); 2193 } 2194 2195 String value = field instanceof ExternalizableField ? ((ReferenceField) ((ExternalizableField) field).getLocalField()).getValue() : ((ReferenceField) field).getValue(); 2196 2197 String valueToValidate = value; 2198 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 2199 { 2200 // Validate external values 2201 value = ((ReferenceField) ((ExternalizableField) field).getExternalField()).getValue(); 2202 } 2203 2204 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valueToValidate)) 2205 { 2206 if (StringUtils.isNotEmpty(value)) 2207 { 2208 form.setField(metadataName, field); 2209 } 2210 } 2211 } 2212 2213 /** 2214 * Bind a reference field from form values 2215 * @param values the form values 2216 * @return The reference field 2217 */ 2218 protected ReferenceField _bindReferenceField (String[] values) 2219 { 2220 Map<String, Object> refValues = Collections.emptyMap(); 2221 if (values.length > 0) 2222 { 2223 refValues = _jsonUtils.convertJsonToMap(values[0]); 2224 } 2225 2226 return new ReferenceField(refValues); 2227 } 2228 2229 /** 2230 * Bind and validate a content reference metadata. 2231 * @param allErrors for storing validation errors. 2232 * @param form the form. 2233 * @param content the content 2234 * @param metadataDefinition the metadata definition. 2235 * @param metadataPath the metadata path from the content. 2236 * @param rawValue the submitted value. 2237 * @param rawValues the raw values of the form 2238 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 2239 * @throws AmetysRepositoryException if an error occurs. 2240 * @throws WorkflowException if an error occurs. 2241 */ 2242 @SuppressWarnings("unchecked") 2243 protected void _bindAndValidateContentReferenceMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 2244 { 2245 String metadataName = metadataDefinition.getName(); 2246 2247 String cTypeId = metadataDefinition.getContentType(); 2248 Collection<String> validContentTypes = new HashSet<>(); 2249 if (cTypeId != null) 2250 { 2251 validContentTypes.add(cTypeId); 2252 validContentTypes.addAll(_contentTypeExtensionPoint.getSubTypes(cTypeId)); 2253 } 2254 2255 AbstractField field = null; 2256 if (externalizable) 2257 { 2258 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 2259 SimpleField<Content> localField = _bindContentField(localValues, cTypeId, validContentTypes, metadataPath, allErrors); 2260 2261 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 2262 SimpleField<Content> extField = _bindContentField(extValues, cTypeId, validContentTypes, metadataPath, allErrors); 2263 2264 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 2265 } 2266 else 2267 { 2268 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 2269 field = _bindContentField(metadataValues, cTypeId, validContentTypes, metadataPath, allErrors); 2270 } 2271 2272 Content[] contentValues = field instanceof ExternalizableField ? ((SimpleField<Content>) ((ExternalizableField) field).getLocalField()).getValues() : ((SimpleField<Content>) field).getValues(); 2273 Content[] contentValuesToValidate = contentValues; 2274 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 2275 { 2276 // Validate external values 2277 contentValuesToValidate = ((SimpleField<Content>) ((ExternalizableField) field).getExternalField()).getValues(); 2278 } 2279 2280 String metadataParamsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 2281 String metadataMode = (String) rawValues.get(metadataParamsPrefix + ".mode"); 2282 field.setMode(metadataMode); 2283 2284 List<Content> oldValues = _getContentValues(content.getMetadataHolder(), metadataPath); 2285 List<Content> valuesToValidate = new ArrayList<>(); 2286 2287 if (field.getMode() == MODE.INSERT && metadataDefinition.isMultiple()) 2288 { 2289 valuesToValidate.addAll(oldValues); 2290 valuesToValidate.addAll(Arrays.asList(contentValues)); 2291 } 2292 else if (field.getMode() == MODE.REMOVE) 2293 { 2294 valuesToValidate.addAll(oldValues); 2295 valuesToValidate.removeAll(Arrays.asList(contentValues)); 2296 } 2297 else 2298 { 2299 valuesToValidate.addAll(Arrays.asList(contentValuesToValidate)); 2300 } 2301 2302 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, valuesToValidate.toArray(new Content[valuesToValidate.size()]))) 2303 { 2304 if (metadataDefinition.isMultiple() || externalizable || contentValues.length > 0) 2305 { 2306 form.setField(metadataName, field); 2307 } 2308 } 2309 } 2310 2311 /** 2312 * Bind a content field from form values 2313 * @param values the form values 2314 * @param cTypeId The id of content type 2315 * @param validContentTypes The valid content types 2316 * @param metadataPath The path of metadata 2317 * @param allErrors for storing validation errors. 2318 * @return The content field 2319 */ 2320 protected SimpleField<Content> _bindContentField (String[] values, String cTypeId, Collection<String> validContentTypes, String metadataPath, AllErrors allErrors) 2321 { 2322 Set<Content> contentList = new LinkedHashSet<>(); 2323 2324 for (String value : values) 2325 { 2326 if (StringUtils.isNotEmpty(value)) 2327 { 2328 try 2329 { 2330 AmetysObject ao = _resolver.resolveById(value); 2331 2332 if (ao instanceof Content) 2333 { 2334 Content contentMeta = (Content) ao; 2335 2336 String[] contentCTypes = ArrayUtils.addAll(contentMeta.getTypes(), contentMeta.getMixinTypes()); 2337 if (cTypeId != null && CollectionUtils.intersection(validContentTypes, Arrays.asList(contentCTypes)).isEmpty()) 2338 { 2339 Errors parseErrors = new Errors(); 2340 2341 List<String> parameters = new ArrayList<>(); 2342 parameters.add(_contentHelper.getTitle(contentMeta)); 2343 parameters.add(contentMeta.getId()); 2344 parameters.add(cTypeId); 2345 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_CONTENTREFERENCE_BADTYPED", parameters)); 2346 allErrors.addError(metadataPath, parseErrors); 2347 } 2348 else 2349 { 2350 contentList.add(contentMeta); 2351 } 2352 } 2353 else 2354 { 2355 Errors parseErrors = new Errors(); 2356 2357 List<String> parameters = new ArrayList<>(); 2358 parameters.add(value); 2359 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_CONTENTREFERENCE_NOTCONTENT", parameters)); 2360 allErrors.addError(metadataPath, parseErrors); 2361 } 2362 } 2363 catch (AmetysRepositoryException e) 2364 { 2365 _logger.error(String.format("Content reference invalid at path '%s', value '%s'", metadataPath, value), e); 2366 2367 Errors parseErrors = new Errors(); 2368 2369 List<String> parameters = new ArrayList<>(); 2370 parameters.add(value); 2371 parseErrors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_CONTENTREFERENCE_INVALID", parameters)); 2372 allErrors.addError(metadataPath, parseErrors); 2373 } 2374 } 2375 } 2376 2377 Content[] contentValues = contentList.toArray(new Content[contentList.size()]); 2378 2379 return new SimpleField<>(contentValues); 2380 } 2381 2382 /** 2383 * Get the content values 2384 * @param compositeMetadata The composite metadata 2385 * @param metadataPath The path of metadata 2386 * @return the list of content values 2387 */ 2388 protected List<Content> _getContentValues (CompositeMetadata compositeMetadata, String metadataPath) 2389 { 2390 List<Content> values = new ArrayList<>(); 2391 2392 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 2393 2394 if (pathSegments.length == 0) 2395 { 2396 return values; 2397 } 2398 2399 CompositeMetadata parentCompositeMetadata = compositeMetadata; 2400 for (int i = 1; i < pathSegments.length - 1; i++) 2401 { 2402 if (parentCompositeMetadata.hasMetadata(pathSegments[i])) 2403 { 2404 parentCompositeMetadata = parentCompositeMetadata.getCompositeMetadata(pathSegments[i]); 2405 } 2406 else 2407 { 2408 // Metadata does not exist, no content values 2409 return values; 2410 } 2411 } 2412 2413 if (parentCompositeMetadata.hasMetadata(pathSegments[pathSegments.length - 1])) 2414 { 2415 String[] contentIds = parentCompositeMetadata.getStringArray(pathSegments[pathSegments.length - 1], new String[0]); 2416 for (String contentId : contentIds) 2417 { 2418 if (StringUtils.isNotEmpty(contentId)) 2419 { 2420 try 2421 { 2422 Content content = _resolver.resolveById(contentId); 2423 values.add(content); 2424 } 2425 catch (UnknownAmetysObjectException e) 2426 { 2427 _logger.warn("The content with id " + contentId + " does not exist anymore. It will be removed from metadata " + metadataPath); 2428 } 2429 catch (AmetysRepositoryException e) 2430 { 2431 throw new AmetysRepositoryException("Cannot edit metadata '" + metadataPath + "'", e); 2432 } 2433 } 2434 } 2435 } 2436 2437 return values; 2438 } 2439 2440 /** 2441 * Bind and validate a content metadata. 2442 * @param allErrors for storing validation errors. 2443 * @param form the form. 2444 * @param content the content 2445 * @param metadataDefinition the metadata definition. 2446 * @param metadataPath the metadata path from the content. 2447 * @param rawValue the submitted value. 2448 * @param rawValues the raw values. 2449 * @param externalizable <code>true</code> true if the metadata is an externalizable metadata (local and external value) 2450 * @throws AmetysRepositoryException if an error occurs. 2451 * @throws WorkflowException if an error occurs. 2452 */ 2453 protected void _bindAndValidateSubContentMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, Object rawValue, Map<String, Object> rawValues, boolean externalizable) throws AmetysRepositoryException, WorkflowException 2454 { 2455 String metadataName = metadataDefinition.getName(); 2456 String cTypeId = metadataDefinition.getContentType(); 2457 Collection<String> validContentTypes = new HashSet<>(); 2458 if (cTypeId != null) 2459 { 2460 validContentTypes.add(cTypeId); 2461 validContentTypes.addAll(_contentTypeExtensionPoint.getSubTypes(cTypeId)); 2462 } 2463 2464 AbstractField field = null; 2465 if (externalizable) 2466 { 2467 String[] localValues = _getLocalValues((String) rawValue, metadataDefinition); 2468 SubContentField localField = _bindSubContentField(localValues, metadataDefinition, metadataPath, rawValues); 2469 2470 String[] extValues = _getExternalValues((String) rawValue, metadataDefinition); 2471 SubContentField extField = _bindSubContentField(extValues, metadataDefinition, metadataPath, rawValues); 2472 2473 field = new ExternalizableField(localField, extField, _getExternalizableStatus((String) rawValue)); 2474 } 2475 else 2476 { 2477 String[] metadataValues = _getMetadataValues(rawValue, metadataDefinition); 2478 field = _bindSubContentField(metadataValues, metadataDefinition, metadataPath, rawValues); 2479 } 2480 2481 if (field != null) 2482 { 2483 List<Map<String, Object>> contentValues = field instanceof ExternalizableField ? ((SubContentField) ((ExternalizableField) field).getLocalField()).getContentValues() : ((SubContentField) field).getContentValues(); 2484 2485 List<Map<String, Object>> contentValuesToValidate = contentValues; 2486 if (field instanceof ExternalizableField && ((ExternalizableField) field).getStatus() == ExternalizableMetadataStatus.EXTERNAL) 2487 { 2488 // Validate external values 2489 contentValuesToValidate = ((SubContentField) ((ExternalizableField) field).getExternalField()).getContentValues(); 2490 } 2491 2492 String paramsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 2493 String metadataMode = (String) rawValues.get(paramsPrefix + ".mode"); 2494 field.setMode(metadataMode); 2495 2496 if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, contentValuesToValidate)) 2497 { 2498 form.setField(metadataName, field); 2499 } 2500 2501 } 2502 } 2503 2504 /** 2505 * Bind a sub-content field from form values 2506 * @param values the form values 2507 * @param metadataDef The metadata definition 2508 * @param metadataPath The path of metadata 2509 * @param rawValues The raw values 2510 * @return The sub-content field 2511 */ 2512 protected SubContentField _bindSubContentField (String[] values, MetadataDefinition metadataDef, String metadataPath, Map<String, Object> rawValues) 2513 { 2514 List<Map<String, Object>> contentValues = new ArrayList<>(); 2515 Set<String> contentIds = new HashSet<>(); 2516 2517 for (String metadataValue : values) 2518 { 2519 Map<String, Object> contentValue = _jsonUtils.convertJsonToMap(metadataValue); 2520 2521 String contentId = null; 2522 if (contentValue.containsKey("id")) 2523 { 2524 // Avoid duplicates id 2525 contentId = (String) contentValue.get("id"); 2526 if (!contentIds.contains(contentId)) 2527 { 2528 contentIds.add(contentId); 2529 contentValues.add(contentValue); 2530 } 2531 } 2532 else 2533 { 2534 contentValues.add(contentValue); 2535 } 2536 } 2537 2538 if (metadataDef.isMultiple() || !contentValues.isEmpty()) 2539 { 2540 String paramsPrefix = INTERNAL_FORM_ELEMENTS_PREFIX + metadataPath.replace('/', '.'); 2541 String contentLanguage = (String) rawValues.get(paramsPrefix + ".contentLanguage"); 2542 Integer initWorkflowActionId = (Integer) rawValues.get(paramsPrefix + ".initWorkflowActionId"); 2543 String workflowName = (String) rawValues.get(paramsPrefix + ".workflowName"); 2544 2545 SubContentField subContentField = new SubContentField(contentValues, contentLanguage); 2546 subContentField.setWorkflow(workflowName, initWorkflowActionId); 2547 2548 return subContentField; 2549 } 2550 2551 return null; 2552 } 2553 2554 /** 2555 * Validate a metadata value. 2556 * @param content the content 2557 * @param metadataDefinition the metadata definition. 2558 * @param metadataPath the metadata path. 2559 * @param allErrors the errors. 2560 * @param value the value. 2561 * @return <code>true</code> if the validation is successful, 2562 * <code>false</code> otherwise. 2563 * @throws WorkflowException if an error occurs. 2564 */ 2565 protected boolean _validateMetadata(Content content, MetadataDefinition metadataDefinition, String metadataPath, AllErrors allErrors, Object value) throws WorkflowException 2566 { 2567 Validator validator = metadataDefinition.getValidator(); 2568 2569 if (validator != null) 2570 { 2571 Errors errors = new Errors(); 2572 2573 if (value != null && value.getClass().isArray() && !metadataDefinition.isMultiple()) 2574 { 2575 Object singleValue = null; 2576 if (Array.getLength(value) != 0) 2577 { 2578 singleValue = Array.get(value, 0); 2579 } 2580 2581 validator.validate(singleValue, errors); 2582 } 2583 else 2584 { 2585 validator.validate(value, errors); 2586 } 2587 2588 if (errors.hasErrors()) 2589 { 2590 allErrors.addError(metadataPath, errors); 2591 2592 return false; 2593 } 2594 } 2595 2596 return true; 2597 } 2598 2599 /** 2600 * Prepare to synchronize a metadata with a composite metadata. 2601 * @param content the content. 2602 * @param metadata the composite metadata to synchronize. 2603 * @param form the form. 2604 * @param allErrors the errors. 2605 * @param user the user. 2606 * @param metadataSetElement the metadata set element for this metadata. 2607 * @param metadataDefinition the metadata definition. 2608 * @param metadataPath the metadata path. 2609 * @param invertEditActionId The action id for editing invert relation 2610 * @throws WorkflowException if an error occurs. 2611 * @throws AmetysRepositoryException If an error occurred 2612 */ 2613 protected void _prepareSynchronizeMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId) throws WorkflowException, AmetysRepositoryException 2614 { 2615 MetadataType type = metadataDefinition.getType(); 2616 switch (type) 2617 { 2618 case CONTENT: 2619 _prepareSynchronizeContentReferenceMetadata(content, metadata, form, allErrors, user, metadataDefinition, metadataPath, invertEditActionId); 2620 break; 2621 2622 case COMPOSITE: 2623 _prepareSynchronizeCompositeMetadata(content, metadata, form, allErrors, user, metadataSetElement, metadataDefinition, metadataPath, invertEditActionId); 2624 break; 2625 2626 default: 2627 break; 2628 } 2629 } 2630 2631 /** 2632 * Synchronize a metadata with a composite metadata. 2633 * @param content the content. 2634 * @param metadata the composite metadata to synchronize. 2635 * @param form the form. 2636 * @param allErrors the errors. 2637 * @param user the user. 2638 * @param metadataSetElement the metadata set element for this metadata. 2639 * @param metadataDefinition the metadata definition. 2640 * @param metadataPath the metadata path. 2641 * @param invertEditActionId The action id for editing invert relation 2642 * @param externalAndLocalMetadata The paths of local and externam metadata 2643 * @throws WorkflowException if an error occurs. 2644 */ 2645 protected void _synchronizeMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, Set<String> externalAndLocalMetadata) throws WorkflowException 2646 { 2647 MetadataType type = metadataDefinition.getType(); 2648 2649 boolean externalizable = externalAndLocalMetadata.contains(metadataPath); 2650 2651 switch (type) 2652 { 2653 case STRING: 2654 _synchronizeStringMetadata(metadata, form, metadataDefinition, externalizable); 2655 break; 2656 case MULTILINGUAL_STRING: 2657 _synchronizeMultilingualStringMetadata(metadata, form, metadataDefinition, externalizable); 2658 break; 2659 case USER: 2660 _synchronizeUserMetadata(metadata, form, metadataDefinition, externalizable); 2661 break; 2662 case DATE: 2663 _synchronizeDateMetadata(metadata, form, metadataDefinition, externalizable); 2664 break; 2665 case DATETIME: 2666 _synchronizeDateMetadata(metadata, form, metadataDefinition, externalizable); 2667 break; 2668 case LONG: 2669 _synchronizeLongMetadata(metadata, form, metadataDefinition, externalizable); 2670 break; 2671 case DOUBLE: 2672 _synchronizeDoubleMetadata(metadata, form, metadataDefinition, externalizable); 2673 break; 2674 case BOOLEAN: 2675 _synchronizeBooleanMetadata(metadata, form, metadataDefinition, externalizable); 2676 break; 2677 case BINARY: 2678 _synchronizeBinaryMetadata(metadata, form, allErrors, user, metadataDefinition, metadataPath, externalizable); 2679 break; 2680 case FILE: 2681 _synchronizeFileMetadata(metadata, form, allErrors, user, metadataDefinition, metadataPath, externalizable); 2682 break; 2683 case RICH_TEXT: 2684 _synchronizeRichTextMetadata(metadata, form, allErrors, user, metadataDefinition, metadataPath, externalizable); 2685 break; 2686 case CONTENT: 2687 _synchronizeContentReferenceMetadata(content, metadata, form, allErrors, user, metadataDefinition, metadataPath, invertEditActionId, externalizable); 2688 break; 2689 case SUB_CONTENT: 2690 _synchronizeSubContentMetadata(content, metadata, form, allErrors, user, metadataDefinition, metadataPath, externalizable); 2691 break; 2692 case GEOCODE: 2693 _synchronizeGeocodeMetadata(metadata, form, metadataDefinition, externalizable); 2694 break; 2695 case REFERENCE: 2696 _synchronizeReferenceMetadata(metadata, form, metadataDefinition, externalizable); 2697 break; 2698 case COMPOSITE: 2699 _synchronizeCompositeMetadata(content, metadata, form, allErrors, user, metadataSetElement, metadataDefinition, metadataPath, invertEditActionId, externalAndLocalMetadata); 2700 break; 2701 default: 2702 throw new WorkflowException("Unsupported type: " + type); 2703 } 2704 2705 // Synchronize metadata comments 2706 _synchronizeMetadataComments(metadata, form, metadataDefinition); 2707 } 2708 2709 /** 2710 * Synchronize the comments of a field. 2711 * @param metadata the metadata. 2712 * @param form the form containing the field. 2713 * @param metadataDefinition the metadata definition. 2714 */ 2715 protected void _synchronizeMetadataComments(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition) 2716 { 2717 String metadataName = metadataDefinition.getName(); 2718 MetadataComment[] comments = form.getCommentArray(metadataName); 2719 2720 if (metadata instanceof CommentableCompositeMetadata) 2721 { 2722 CommentableCompositeMetadata commentableMetadata = (CommentableCompositeMetadata) metadata; 2723 int index = 1; 2724 2725 // Do not modify comments if no info. However, with an empty array, all existing comments will be removed. 2726 if (comments != null) 2727 { 2728 // Add / edit comments 2729 for (MetadataComment comment : comments) 2730 { 2731 if (commentableMetadata.hasComment(metadataName, index)) 2732 { 2733 commentableMetadata.editComment(metadataName, index, comment.getComment(), comment.getAuthor(), comment.getDate()); 2734 } 2735 else 2736 { 2737 commentableMetadata.addComment(metadataName, comment.getComment(), comment.getAuthor(), comment.getDate()); 2738 } 2739 2740 index++; 2741 } 2742 2743 // Delete remaining comments 2744 while (commentableMetadata.hasComment(metadataName, index)) 2745 { 2746 commentableMetadata.deleteComment(metadataName, index); 2747 index++; 2748 } 2749 } 2750 } 2751 2752 } 2753 2754 /** 2755 * Synchronize a composite-typed metadata with a a composite metadata. 2756 * @param content the content. 2757 * @param metadata the composite metadata to synchronize. 2758 * @param form the form. 2759 * @param allErrors the errors. 2760 * @param user the user. 2761 * @param metadataSetElement the metadata set element for this metadata. 2762 * @param metadataDefinition the metadata definition. 2763 * @param metadataPath the metadata path. 2764 * @param editActionId The action id for editing invert relation 2765 * @throws WorkflowException if an error occurs. 2766 */ 2767 protected void _prepareSynchronizeCompositeMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath, int editActionId) throws WorkflowException 2768 { 2769 String metadataName = metadataDefinition.getName(); 2770 2771 if (metadataDefinition instanceof RepeaterDefinition) 2772 { 2773 _prepareSynchronizeRepeater(content, metadata, form, allErrors, user, metadataSetElement, (RepeaterDefinition) metadataDefinition, metadataPath, editActionId); 2774 } 2775 else 2776 { 2777 Form compositeForm = form.getCompositeField(metadataName); 2778 2779 if (compositeForm != null) 2780 { 2781 ModifiableCompositeMetadata subMetadata = metadata.getCompositeMetadata(metadataName, true); 2782 _prepareSynchronizeMetadataSetElement(content, subMetadata, compositeForm, allErrors, user, metadataSetElement, metadataDefinition, metadataPath + ContentConstants.METADATA_PATH_SEPARATOR, editActionId); 2783 } 2784 else 2785 { 2786 if (metadata.hasMetadata(metadataName)) 2787 { 2788 metadata.removeMetadata(metadataName); 2789 } 2790 } 2791 } 2792 } 2793 2794 /** 2795 * Synchronize a composite-typed metadata with a a composite metadata. 2796 * @param content the content. 2797 * @param metadata the composite metadata to synchronize. 2798 * @param form the form. 2799 * @param allErrors the errors. 2800 * @param user the user. 2801 * @param metadataSetElement the metadata set element for this metadata. 2802 * @param metadataDefinition the metadata definition. 2803 * @param metadataPath the metadata path. 2804 * @param editActionId The action id for editing invert relation 2805 * @param externalAndLocalMetadata The paths of local and externam metadata 2806 * @throws WorkflowException if an error occurs. 2807 */ 2808 protected void _synchronizeCompositeMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath, int editActionId, Set<String> externalAndLocalMetadata) throws WorkflowException 2809 { 2810 String metadataName = metadataDefinition.getName(); 2811 2812 if (metadataDefinition instanceof RepeaterDefinition) 2813 { 2814 _synchronizeRepeater(content, metadata, form, allErrors, user, metadataSetElement, (RepeaterDefinition) metadataDefinition, metadataPath, editActionId, externalAndLocalMetadata); 2815 } 2816 else 2817 { 2818 Form compositeForm = form.getCompositeField(metadataName); 2819 2820 if (compositeForm != null) 2821 { 2822 ModifiableCompositeMetadata subMetadata = metadata.getCompositeMetadata(metadataName, true); 2823 _synchronizeMetadataSetElement(content, subMetadata, compositeForm, allErrors, user, metadataSetElement, metadataDefinition, metadataPath + ContentConstants.METADATA_PATH_SEPARATOR, editActionId, externalAndLocalMetadata); 2824 } 2825 else 2826 { 2827 if (metadata.hasMetadata(metadataName)) 2828 { 2829 metadata.removeMetadata(metadataName); 2830 } 2831 } 2832 } 2833 } 2834 2835 /** 2836 * Synchronize a repeater with a composite metadata. 2837 * @param content the content. 2838 * @param metadata the composite metadata to synchronize. 2839 * @param form the form. 2840 * @param allErrors the errors. 2841 * @param user the user. 2842 * @param metadataSetElement the metadata set element for this metadata. 2843 * @param repeaterDefinition the repeater definition. 2844 * @param metadataPath the metadata path. 2845 * @param invertActionId The current 'edit content' action ID. 2846 * @throws WorkflowException if an error occurs. 2847 */ 2848 protected void _prepareSynchronizeRepeater(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, RepeaterDefinition repeaterDefinition, String metadataPath, int invertActionId) throws WorkflowException 2849 { 2850 String metadataName = repeaterDefinition.getName(); 2851 RepeaterField repeaterField = form.getRepeaterField(metadataName); 2852 2853 List<RepeaterEntry> entries = repeaterField.getEntries(); 2854 2855 ModifiableCompositeMetadata repeaterMetadata = metadata.getCompositeMetadata(metadataName, true); 2856 2857 // Add new entries 2858 for (RepeaterEntry repeaterEntry : entries) 2859 { 2860 int computedPosition = _computeRepeaterEntryPosition(repeaterMetadata, repeaterEntry); 2861 2862 try 2863 { 2864 ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(String.valueOf(computedPosition)); 2865 _prepareSynchronizeMetadataSetElement(content, entryMetadata, repeaterEntry, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath + "/" + computedPosition + "/", invertActionId); 2866 } 2867 catch (UnknownMetadataException e) 2868 { 2869 ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(String.valueOf(computedPosition), true); 2870 _prepareSynchronizeMetadataSetElement(content, entryMetadata, repeaterEntry, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath + "/" + computedPosition + "/", invertActionId); 2871 2872 // Then remove created entry 2873 repeaterMetadata.removeMetadata(String.valueOf(computedPosition)); 2874 } 2875 } 2876 } 2877 2878 2879 /** 2880 * Synchronize a repeater with a composite metadata. 2881 * @param content the content. 2882 * @param metadata the composite metadata to synchronize. 2883 * @param form the form. 2884 * @param allErrors the errors. 2885 * @param user the user. 2886 * @param metadataSetElement the metadata set element for this metadata. 2887 * @param repeaterDefinition the repeater definition. 2888 * @param metadataPath the metadata path. 2889 * @param editActionId The current 'edit content' action ID. 2890 * @param externalAndLocalMetadata The paths of local and externam metadata 2891 * @throws WorkflowException if an error occurs. 2892 */ 2893 protected void _synchronizeRepeater(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, RepeaterDefinition repeaterDefinition, String metadataPath, int editActionId, Set<String> externalAndLocalMetadata) throws WorkflowException 2894 { 2895 String metadataName = repeaterDefinition.getName(); 2896 RepeaterField repeaterField = form.getRepeaterField(metadataName); 2897 2898 _checkRepeaterSize(metadata, form, allErrors, repeaterDefinition, metadataPath); 2899 2900 switch (repeaterField.getMode()) 2901 { 2902 case REPLACE: 2903 _synchronizeRepeaterInReplaceMode(content, metadata, form, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath, editActionId, externalAndLocalMetadata); 2904 break; 2905 case REMOVE: 2906 _synchronizeRepeaterInRemoveMode(content, metadata, form, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath); 2907 break; 2908 case INSERT: 2909 default: 2910 _synchronizeRepeaterInInsertMode(content, metadata, form, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath, editActionId, externalAndLocalMetadata); 2911 break; 2912 } 2913 } 2914 2915 /** 2916 * Check the repeater size will be correct 2917 * @param metadata The metadata values 2918 * @param form the form 2919 * @param allErrors the list of errors 2920 * @param repeaterDefinition the definition of the repeater 2921 * @param metadataPath The path of the metadata 2922 */ 2923 protected void _checkRepeaterSize(ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, RepeaterDefinition repeaterDefinition, String metadataPath) 2924 { 2925 String metadataName = repeaterDefinition.getName(); 2926 ModifiableCompositeMetadata repeaterMetadata = metadata.getCompositeMetadata(metadataName, true); 2927 RepeaterField repeaterField = form.getRepeaterField(metadataName); 2928 2929 // Check size 2930 int minSize = repeaterDefinition.getMinSize(); 2931 2932 int newSize = (repeaterField.getMode() == MODE.REPLACE ? repeaterField.getEntries().size() : repeaterMetadata.getMetadataNames().length) 2933 + (repeaterField.getMode() == MODE.INSERT ? repeaterField.getEntries().size() : 0) 2934 + (repeaterField.getMode() == MODE.REMOVE ? -repeaterField.getEntries().size() : 0); 2935 2936 // Min size validation 2937 if (newSize < minSize) 2938 { 2939 Errors errors = new Errors(); 2940 2941 List<String> parameters = new ArrayList<>(); 2942 parameters.add(metadataName); 2943 parameters.add(Integer.toString(minSize)); 2944 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_REPEATER_MINSIZE", parameters)); 2945 allErrors.addError(metadataPath, errors); 2946 } 2947 // Max size validation 2948 int maxSize = repeaterDefinition.getMaxSize(); 2949 if (maxSize > 0 && newSize > maxSize) 2950 { 2951 Errors errors = new Errors(); 2952 2953 List<String> parameters = new ArrayList<>(); 2954 parameters.add(metadataName); 2955 parameters.add(Integer.toString(maxSize)); 2956 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_REPEATER_MAXSIZE", parameters)); 2957 allErrors.addError(metadataPath, errors); 2958 } 2959 } 2960 2961 /** 2962 * Synchronize a repeater with a composite metadata, when the values has to be added to existing ones. 2963 * @param content the content. 2964 * @param metadata the composite metadata to synchronize. 2965 * @param form the form. 2966 * @param allErrors the errors. 2967 * @param user the user. 2968 * @param metadataSetElement the metadata set element for this metadata. 2969 * @param repeaterDefinition the repeater definition. 2970 * @param metadataPath the metadata path. 2971 * @param editActionId The current 'edit content' action ID. 2972 * @param externalAndLocalMetadata The paths of local and externam metadata 2973 * @throws WorkflowException if an error occurs. 2974 */ 2975 protected void _synchronizeRepeaterInInsertMode(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, RepeaterDefinition repeaterDefinition, String metadataPath, int editActionId, Set<String> externalAndLocalMetadata) throws WorkflowException 2976 { 2977 String metadataName = repeaterDefinition.getName(); 2978 RepeaterField repeaterField = form.getRepeaterField(metadataName); 2979 List<RepeaterEntry> entries = repeaterField.getEntries(); 2980 2981 ModifiableCompositeMetadata repeaterMetadata = metadata.getCompositeMetadata(metadataName, true); 2982 2983 // Add new entries 2984 for (RepeaterEntry repeaterEntry : entries) 2985 { 2986 int computedPosition = _computeRepeaterEntryPosition(repeaterMetadata, repeaterEntry); 2987 2988 // Is somebody in the way? 2989 for (int i = repeaterMetadata.getMetadataNames().length; i >= computedPosition; i--) 2990 { 2991 // move to the next position 2992 _moveRepeaterEntry(repeaterMetadata, String.valueOf(i), String.valueOf(i + 1)); 2993 } 2994 2995 // New entry (computedPosition is free) 2996 ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(String.valueOf(computedPosition), true); 2997 _synchronizeMetadataSetElement(content, entryMetadata, repeaterEntry, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath + "/" + computedPosition + "/", editActionId, externalAndLocalMetadata); 2998 } 2999 } 3000 3001 /** 3002 * Synchronize a repeater with a composite metadata, when the values has to removed from existing ones. 3003 * @param content the content. 3004 * @param metadata the composite metadata to synchronize. 3005 * @param form the form. 3006 * @param allErrors the errors. 3007 * @param user the user. 3008 * @param metadataSetElement the metadata set element for this metadata. 3009 * @param repeaterDefinition the repeater definition. 3010 * @param metadataPath the metadata path. 3011 * @throws WorkflowException if an error occurs. 3012 */ 3013 protected void _synchronizeRepeaterInRemoveMode(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, RepeaterDefinition repeaterDefinition, String metadataPath) throws WorkflowException 3014 { 3015 String metadataName = repeaterDefinition.getName(); 3016 RepeaterField repeaterField = form.getRepeaterField(metadataName); 3017 List<RepeaterEntry> entries = repeaterField.getEntries(); 3018 3019 ModifiableCompositeMetadata repeaterMetadata = metadata.getCompositeMetadata(metadataName, true); 3020 3021 // Add new entries 3022 for (RepeaterEntry repeaterEntry : entries) 3023 { 3024 int computedPosition = _computeRepeaterEntryPosition(repeaterMetadata, repeaterEntry); 3025 3026 // remove 3027 repeaterMetadata.removeMetadata(String.valueOf(computedPosition)); 3028 3029 // Is there a hole? 3030 for (int i = computedPosition + 1; i < repeaterMetadata.getMetadataNames().length; i++) 3031 { 3032 // move to the next position 3033 _moveRepeaterEntry(repeaterMetadata, String.valueOf(i), String.valueOf(i - 1)); 3034 } 3035 } 3036 3037 } 3038 3039 private int _computeRepeaterEntryPosition(ModifiableCompositeMetadata repeaterMetadata, RepeaterEntry repeaterEntry) 3040 { 3041 int position = repeaterEntry.getPosition(); 3042 // -1, -2, -3... means 1, 2, 3 before the end 3043 // 0 means at the end 3044 // 1, 2, 3... means at position 1, 2 or 3 3045 int computedPosition; 3046 if (position > 0) 3047 { 3048 computedPosition = position; 3049 } 3050 else 3051 { 3052 computedPosition = repeaterMetadata.getMetadataNames().length + 1 - position; 3053 } 3054 3055 computedPosition = Math.max(Math.min(computedPosition, repeaterMetadata.getMetadataNames().length + 1), 1); 3056 return computedPosition; 3057 } 3058 3059 /** 3060 * Synchronize a repeater with a composite metadata, when the values has to replace existing ones. 3061 * @param content the content. 3062 * @param metadata the composite metadata to synchronize. 3063 * @param form the form. 3064 * @param allErrors the errors. 3065 * @param user the user. 3066 * @param metadataSetElement the metadata set element for this metadata. 3067 * @param repeaterDefinition the repeater definition. 3068 * @param metadataPath the metadata path. 3069 * @param editActionId The current 'edit content' action ID. 3070 * @param externalAndLocalMetadata The paths of local and externam metadata 3071 * @throws WorkflowException if an error occurs. 3072 */ 3073 protected void _synchronizeRepeaterInReplaceMode(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, AbstractMetadataSetElement metadataSetElement, RepeaterDefinition repeaterDefinition, String metadataPath, int editActionId, Set<String> externalAndLocalMetadata) throws WorkflowException 3074 { 3075 String metadataName = repeaterDefinition.getName(); 3076 RepeaterField repeaterField = form.getRepeaterField(metadataName); 3077 List<RepeaterEntry> entries = repeaterField.getEntries(); 3078 Map<String, String> laterMoves = new HashMap<>(); 3079 3080 ModifiableCompositeMetadata repeaterMetadata = metadata.getCompositeMetadata(metadataName, true); 3081 3082 for (String entryName : repeaterMetadata.getMetadataNames()) 3083 { 3084 // Only process composite metadata 3085 if (repeaterMetadata.getType(entryName) == org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.COMPOSITE) 3086 { 3087 // -1 is returns for custom composite metadata on a repeater which is not an entry 3088 int currentPosition = NumberUtils.toInt(entryName, -1); 3089 3090 if (currentPosition != -1) 3091 { 3092 RepeaterEntry repeaterEntry = _getEntry(entries, entryName); 3093 3094 if (repeaterEntry == null) 3095 { 3096 // Do additional processing to remove entry sub-metadatas. 3097 ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(entryName); 3098 _synchronizeMetadataRemoval(content, entryMetadata, repeaterEntry, allErrors, user, repeaterDefinition, metadataPath + "/" + entryName + "/", editActionId); 3099 3100 // This entry does not exist anymore 3101 repeaterMetadata.removeMetadata(entryName); 3102 } 3103 else 3104 { 3105 int newPosition = repeaterEntry.getPosition(); 3106 3107 // Check if position has changed 3108 if (newPosition != currentPosition) 3109 { 3110 String finalEntryName = String.valueOf(newPosition); 3111 String tempEntryName = finalEntryName; 3112 3113 // Check if there is already an entry by that name 3114 if (!repeaterMetadata.hasMetadata(String.valueOf(newPosition))) 3115 { 3116 // Move the composite to the new name 3117 _moveRepeaterEntry(repeaterMetadata, entryName, finalEntryName); 3118 } 3119 else 3120 { 3121 // Move to a temporary name 3122 tempEntryName = "temp-" + String.valueOf(newPosition); 3123 _moveRepeaterEntry(repeaterMetadata, entryName, tempEntryName); 3124 // Keep in mind for later move 3125 laterMoves.put(tempEntryName, finalEntryName); 3126 } 3127 } 3128 } 3129 } 3130 } 3131 } 3132 3133 // Process moves 3134 for (Map.Entry<String, String> move : laterMoves.entrySet()) 3135 { 3136 // Move to a temporary name 3137 _moveRepeaterEntry(repeaterMetadata, move.getKey(), move.getValue()); 3138 } 3139 3140 for (String entryName : repeaterMetadata.getMetadataNames()) 3141 { 3142 // Only process composite metadata 3143 if (repeaterMetadata.getType(entryName) == org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.COMPOSITE) 3144 { 3145 int currentPosition = NumberUtils.toInt(entryName, -1); 3146 3147 if (currentPosition != -1) 3148 { 3149 RepeaterEntry repeaterEntry = _getCurrentEntry(entries, entryName); 3150 3151 if (repeaterEntry != null) 3152 { 3153 // Synchronization 3154 ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(entryName, true); 3155 _synchronizeMetadataSetElement(content, entryMetadata, repeaterEntry, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath + "/" + entryName + "/", editActionId, externalAndLocalMetadata); 3156 } 3157 } 3158 } 3159 } 3160 3161 // Creates new entries 3162 for (RepeaterEntry repeaterEntry : entries) 3163 { 3164 if (repeaterEntry.getPreviousPosition() == -1) 3165 { 3166 int position = repeaterEntry.getPosition(); 3167 // New entry 3168 ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(String.valueOf(position), true); 3169 _synchronizeMetadataSetElement(content, entryMetadata, repeaterEntry, allErrors, user, metadataSetElement, repeaterDefinition, metadataPath + "/" + position + "/", editActionId, externalAndLocalMetadata); 3170 } 3171 } 3172 } 3173 3174 /** 3175 * Do additional processing to remove entry sub-metadatas. 3176 * @param content the processed content. 3177 * @param metadata the composite metadata being removed. 3178 * @param form the form. 3179 * @param allErrors the errors. 3180 * @param user the user. 3181 * @param parentMetadataDefinition the parent metadata definition. 3182 * @param metadataPath the metadata path. 3183 * @param editActionId The current 'edit content' action ID. 3184 * @throws WorkflowException if an error occurs. 3185 */ 3186 protected void _synchronizeMetadataRemoval(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, MetadataDefinition parentMetadataDefinition, String metadataPath, int editActionId) throws WorkflowException 3187 { 3188 // Currently only purpose is to handle 3189 if (_invertRelationEnabled()) 3190 { 3191 // Use the real metadatas instead of the metadata-set, because they will be physically removed 3192 // even if they aren't in the metadata-set. 3193 for (String subMetadataName : parentMetadataDefinition.getMetadataNames()) 3194 { 3195 MetadataDefinition metadataDefinition = parentMetadataDefinition.getMetadataDefinition(subMetadataName); 3196 if (metadataDefinition != null) 3197 { 3198 String subMetadataPath = metadataPath + subMetadataName; 3199 if (metadata.hasMetadata(subMetadataName)) 3200 { 3201 if (metadataDefinition.getType() == MetadataType.COMPOSITE) 3202 { 3203 // Recurse in composites. 3204 ModifiableCompositeMetadata compositeMetadata = metadata.getCompositeMetadata(subMetadataName); 3205 _synchronizeMetadataRemoval(content, compositeMetadata, form, allErrors, user, metadataDefinition, subMetadataPath + "/", editActionId); 3206 } 3207 else if (metadataDefinition.getType() == MetadataType.CONTENT) 3208 { 3209 // In case of existing mutual relationship, remove it. 3210 String[] refContentIds = metadata.getStringArray(subMetadataName, new String[0]); 3211 _removeInvertRelations(content.getId(), metadataDefinition, subMetadataPath, refContentIds, editActionId, allErrors); 3212 } 3213 } 3214 } 3215 } 3216 } 3217 } 3218 3219 /** 3220 * Move a repeater entry. 3221 * @param metadata the parent composite metadata. 3222 * @param fromName the current entry name. 3223 * @param toName the new entry name. 3224 * @throws WorkflowException if an error occurs. 3225 */ 3226 protected void _moveRepeaterEntry(CompositeMetadata metadata, String fromName, String toName) throws WorkflowException 3227 { 3228 // FIXME movablemetadata 3229 if (!(metadata instanceof JCRCompositeMetadata)) 3230 { 3231 throw new WorkflowException("Unable to manage non JCR composite metadata: " + metadata); 3232 } 3233 3234 try 3235 { 3236 Node node = ((JCRCompositeMetadata) metadata).getNode(); 3237 node.getSession().move(node.getNode(JCRCompositeMetadata.METADATA_PREFIX + fromName).getPath(), 3238 node.getPath() + "/" + JCRCompositeMetadata.METADATA_PREFIX + toName); 3239 } 3240 catch (RepositoryException e) 3241 { 3242 throw new WorkflowException("Unable to move repeater entry", e); 3243 } 3244 } 3245 3246 /** 3247 * Retrieves a repeater entry corresponding to an entry name. 3248 * @param entries the entries. 3249 * @param entryName the entry name. 3250 * @return the entry found or <code>null</code> otherwise. 3251 */ 3252 protected RepeaterEntry _getEntry(List<RepeaterEntry> entries, String entryName) 3253 { 3254 int position = Integer.valueOf(entryName); 3255 3256 for (RepeaterEntry entry : entries) 3257 { 3258 int previousPosition = entry.getPreviousPosition(); 3259 3260 if (previousPosition != -1 && previousPosition == position) 3261 { 3262 return entry; 3263 } 3264 } 3265 3266 // Not found 3267 return null; 3268 } 3269 3270 /** 3271 * Retrieves a repeater entry corresponding to an entry name. 3272 * @param entries the entries. 3273 * @param entryName the entry name. 3274 * @return the entry found or <code>null</code> otherwise. 3275 */ 3276 protected RepeaterEntry _getCurrentEntry(List<RepeaterEntry> entries, String entryName) 3277 { 3278 int seekPosition = Integer.valueOf(entryName); 3279 3280 for (RepeaterEntry entry : entries) 3281 { 3282 int position = entry.getPosition(); 3283 3284 if (position != -1 && position == seekPosition) 3285 { 3286 return entry; 3287 } 3288 } 3289 3290 // Not found 3291 return null; 3292 } 3293 3294 /** 3295 * Synchronize a string metadata from a field. 3296 * @param metadata the metadata. 3297 * @param form the form containing the field. 3298 * @param metadataDefinition the metadata definition. 3299 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3300 * @throws WorkflowException if an error occurs. 3301 */ 3302 protected void _synchronizeStringMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3303 { 3304 String metadataName = metadataDefinition.getName(); 3305 3306 SimpleField<String> field = form.getStringArray(metadataName); 3307 3308 if (field != null && field.getValues() != null && (metadataDefinition.isMultiple() || (field.getValues().length > 0 && field.getValues()[0] != null))) 3309 { 3310 if (metadataDefinition.isMultiple()) 3311 { 3312 _synchronizeMultipleStringMetadata(metadata, form, externalizable, metadataName, field); 3313 } 3314 else 3315 { 3316 if (field.getMode() == MODE.REMOVE) 3317 { 3318 metadata.removeMetadata(metadataName); 3319 } 3320 else 3321 { 3322 _setMetadata(metadata, metadataName, form, field.getValues()[0], externalizable); 3323 } 3324 } 3325 } 3326 else 3327 { 3328 if (externalizable) 3329 { 3330 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3331 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 3332 } 3333 3334 _removeMetadataIfExists(metadata, metadataName, externalizable); 3335 } 3336 } 3337 3338 private void _synchronizeMultipleStringMetadata(ModifiableCompositeMetadata metadata, Form form, boolean externalizable, String metadataName, SimpleField<String> field) 3339 { 3340 if (field.getMode() == MODE.REPLACE 3341 || field.getMode() == MODE.INSERT && !metadata.hasMetadata(metadataName)) 3342 { 3343 _setMetadata(metadata, metadataName, form, field.getValues(), externalizable); 3344 } 3345 else if (field.getMode() != MODE.REMOVE || metadata.hasMetadata(metadataName)) 3346 { 3347 String[] array = metadata.getStringArray(metadataName); 3348 if (field.getMode() == MODE.INSERT) 3349 { 3350 array = ArrayUtils.addAll(array, field.getValues()); 3351 } 3352 else 3353 { 3354 array = ArrayUtils.removeElements(array, field.getValues()); 3355 } 3356 3357 if (array.length > 0) 3358 { 3359 _setMetadata(metadata, metadataName, form, array, externalizable); 3360 } 3361 else 3362 { 3363 _removeMetadataIfExists(metadata, metadataName, externalizable); 3364 } 3365 } 3366 } 3367 3368 /** 3369 * Synchronize a string metadata from a field. 3370 * @param metadata the metadata. 3371 * @param form the form containing the field. 3372 * @param metadataDefinition the metadata definition. 3373 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3374 * @throws WorkflowException if an error occurs. 3375 */ 3376 protected void _synchronizeMultilingualStringMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3377 { 3378 String metadataName = metadataDefinition.getName(); 3379 3380 SimpleField<MultilingualString> field = form.getMultilingualStringArray(metadataName); 3381 3382 if (field != null && field.getValues() != null && (field.getValues().length > 0 && field.getValues()[0] != null)) 3383 { 3384 if (field.getMode() == MODE.REMOVE) 3385 { 3386 metadata.removeMetadata(metadataName); 3387 } 3388 else 3389 { 3390 _setMetadata(metadata, metadataName, form, field.getValues()[0], externalizable); 3391 } 3392 } 3393 else 3394 { 3395 if (externalizable) 3396 { 3397 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3398 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 3399 } 3400 3401 _removeMetadataIfExists(metadata, metadataName, externalizable); 3402 } 3403 } 3404 3405 /** 3406 * Synchronize a user metadata from a field. 3407 * @param metadata the metadata. 3408 * @param form the form containing the field. 3409 * @param metadataDefinition the metadata definition. 3410 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3411 * @throws WorkflowException if an error occurs. 3412 */ 3413 protected void _synchronizeUserMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3414 { 3415 String metadataName = metadataDefinition.getName(); 3416 SimpleField<UserIdentity> field = form.getUserArray(metadataName); 3417 3418 if (field != null && field.getValues() != null && (metadataDefinition.isMultiple() || (field.getValues().length > 0 && field.getValues()[0] != null))) 3419 { 3420 if (metadataDefinition.isMultiple()) 3421 { 3422 _synchronizeMultipleUserMetadata(metadata, form, externalizable, metadataName, field); 3423 } 3424 else 3425 { 3426 if (field.getMode() == MODE.REMOVE) 3427 { 3428 metadata.removeMetadata(metadataName); 3429 } 3430 else 3431 { 3432 _setMetadata(metadata, metadataName, form, field.getValues()[0], externalizable); 3433 } 3434 } 3435 } 3436 else 3437 { 3438 if (externalizable) 3439 { 3440 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3441 ExternalizableMetadataHelper.updateCompositeMetadataStatus(metadata, metadataName, status); 3442 } 3443 3444 _removeMetadataIfExists(metadata, metadataName, externalizable); 3445 } 3446 } 3447 3448 private void _synchronizeMultipleUserMetadata(ModifiableCompositeMetadata metadata, Form form, boolean externalizable, String metadataName, SimpleField<UserIdentity> field) 3449 { 3450 if (field.getMode() == MODE.REPLACE 3451 || field.getMode() == MODE.INSERT && !metadata.hasMetadata(metadataName)) 3452 { 3453 _setMetadata(metadata, metadataName, form, field.getValues(), externalizable); 3454 } 3455 else if (field.getMode() != MODE.REMOVE || metadata.hasMetadata(metadataName)) 3456 { 3457 UserIdentity[] array = metadata.getUserArray(metadataName); 3458 if (field.getMode() == MODE.INSERT) 3459 { 3460 array = ArrayUtils.addAll(array, field.getValues()); 3461 } 3462 else 3463 { 3464 array = ArrayUtils.removeElements(array, field.getValues()); 3465 } 3466 3467 if (array.length > 0) 3468 { 3469 _setMetadata(metadata, metadataName, form, array, externalizable); 3470 } 3471 else 3472 { 3473 _removeMetadataIfExists(metadata, metadataName, externalizable); 3474 } 3475 } 3476 } 3477 3478 /** 3479 * Synchronize a date metadata from a field. 3480 * @param metadata the metadata. 3481 * @param form the form containing the field. 3482 * @param metadataDefinition the metadata definition. 3483 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3484 * @throws WorkflowException if an error occurs. 3485 */ 3486 protected void _synchronizeDateMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3487 { 3488 String metadataName = metadataDefinition.getName(); 3489 SimpleField<Date> field = form.getDateArray(metadataName); 3490 3491 if (field != null && field.getValues() != null && (metadataDefinition.isMultiple() || (field.getValues().length > 0 && field.getValues()[0] != null))) 3492 { 3493 if (metadataDefinition.isMultiple()) 3494 { 3495 _synchronizeMultipleDateMetadata(metadata, form, externalizable, metadataName, field); 3496 } 3497 else 3498 { 3499 if (field.getMode() == MODE.REMOVE) 3500 { 3501 metadata.removeMetadata(metadataName); 3502 } 3503 else 3504 { 3505 _setMetadata(metadata, metadataName, form, field.getValues()[0], externalizable); 3506 } 3507 } 3508 } 3509 else 3510 { 3511 if (externalizable) 3512 { 3513 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3514 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 3515 } 3516 3517 _removeMetadataIfExists(metadata, metadataName, externalizable); 3518 } 3519 } 3520 3521 private void _synchronizeMultipleDateMetadata(ModifiableCompositeMetadata metadata, Form form, boolean externalizable, String metadataName, SimpleField<Date> field) 3522 { 3523 if (field.getMode() == MODE.REPLACE 3524 || field.getMode() == MODE.INSERT && !metadata.hasMetadata(metadataName)) 3525 { 3526 _setMetadata(metadata, metadataName, form, field.getValues(), externalizable); 3527 } 3528 else if (field.getMode() != MODE.REMOVE || metadata.hasMetadata(metadataName)) 3529 { 3530 Date[] array = metadata.getDateArray(metadataName); 3531 if (field.getMode() == MODE.INSERT) 3532 { 3533 array = ArrayUtils.addAll(array, field.getValues()); 3534 } 3535 else 3536 { 3537 array = ArrayUtils.removeElements(array, field.getValues()); 3538 } 3539 3540 if (array.length > 0) 3541 { 3542 _setMetadata(metadata, metadataName, form, array, externalizable); 3543 } 3544 else 3545 { 3546 _removeMetadataIfExists(metadata, metadataName, externalizable); 3547 } 3548 } 3549 } 3550 3551 /** 3552 * Remove a metadata if exists. 3553 * Be careful ! If externalizable, this method have to be called after setting the current status. 3554 * @param metadata The parent composite metadata 3555 * @param metadataName The metadata name 3556 * @param externalizable <code>true</code> if externalizable 3557 */ 3558 protected void _removeMetadataIfExists (ModifiableCompositeMetadata metadata, String metadataName, boolean externalizable) 3559 { 3560 if (externalizable) 3561 { 3562 ExternalizableMetadataHelper.removeLocalMetadataIfExists(metadata, metadataName); 3563 } 3564 else 3565 { 3566 if (metadata.hasMetadata(metadataName)) 3567 { 3568 metadata.removeMetadata(metadataName); 3569 } 3570 } 3571 } 3572 3573 /** 3574 * Synchronize a long metadata from a field. 3575 * @param metadata the metadata. 3576 * @param form the form containing the field. 3577 * @param metadataDefinition the metadata definition. 3578 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3579 * @throws WorkflowException if an error occurs. 3580 */ 3581 protected void _synchronizeLongMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3582 { 3583 String metadataName = metadataDefinition.getName(); 3584 SimpleField<Long> field = form.getLongArray(metadataName); 3585 3586 if (field != null && field.getValues() != null && (metadataDefinition.isMultiple() || (field.getValues().length > 0 && field.getValues()[0] != null))) 3587 { 3588 if (metadataDefinition.isMultiple()) 3589 { 3590 _synchronizeMultipleLongMetadata(metadata, form, externalizable, metadataName, field); 3591 } 3592 else 3593 { 3594 if (field.getMode() == MODE.REMOVE) 3595 { 3596 metadata.removeMetadata(metadataName); 3597 } 3598 else 3599 { 3600 _setMetadata(metadata, metadataName, form, field.getValues()[0], externalizable); 3601 } 3602 } 3603 } 3604 else 3605 { 3606 if (externalizable) 3607 { 3608 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3609 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 3610 } 3611 3612 _removeMetadataIfExists(metadata, metadataName, externalizable); 3613 } 3614 } 3615 3616 private void _synchronizeMultipleLongMetadata(ModifiableCompositeMetadata metadata, Form form, boolean externalizable, String metadataName, SimpleField<Long> field) 3617 { 3618 if (field.getMode() == MODE.REPLACE 3619 || field.getMode() == MODE.INSERT && !metadata.hasMetadata(metadataName)) 3620 { 3621 _setMetadata(metadata, metadataName, form, field.getValues(), externalizable); 3622 } 3623 else if (field.getMode() != MODE.REMOVE || metadata.hasMetadata(metadataName)) 3624 { 3625 Long[] array = ArrayUtils.toObject(metadata.getLongArray(metadataName)); 3626 if (field.getMode() == MODE.INSERT) 3627 { 3628 array = ArrayUtils.addAll(array, field.getValues()); 3629 } 3630 else 3631 { 3632 array = ArrayUtils.removeElements(array, field.getValues()); 3633 } 3634 3635 if (array.length > 0) 3636 { 3637 _setMetadata(metadata, metadataName, form, array, externalizable); 3638 } 3639 else 3640 { 3641 _removeMetadataIfExists(metadata, metadataName, externalizable); 3642 } 3643 } 3644 } 3645 3646 /** 3647 * Synchronize a geocode metadata from a field. 3648 * @param metadata the metadata. 3649 * @param form the form containing the field. 3650 * @param metadataDefinition the metadata definition. 3651 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3652 * @throws WorkflowException if an error occurs. 3653 */ 3654 protected void _synchronizeGeocodeMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3655 { 3656 String metadataName = metadataDefinition.getName(); 3657 SimpleField<Double> field = form.getDoubleArray(metadataName); 3658 3659 if (field != null && field.getValues() != null && field.getMode() != MODE.REMOVE) 3660 { 3661 ModifiableCompositeMetadata geoCode = null; 3662 if (externalizable) 3663 { 3664 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3665 geoCode = ExternalizableMetadataHelper.setLocalCompositeMetadata(metadata, metadataName, status); 3666 } 3667 else 3668 { 3669 geoCode = ExternalizableMetadataHelper.getCompositeMetadata(metadata, metadataName); 3670 } 3671 3672 geoCode.setMetadata("longitude", field.getValues()[0]); 3673 geoCode.setMetadata("latitude", field.getValues()[1]); 3674 } 3675 else 3676 { 3677 if (externalizable) 3678 { 3679 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3680 ExternalizableMetadataHelper.updateCompositeMetadataStatus(metadata, metadataName, status); 3681 } 3682 3683 _removeMetadataIfExists(metadata, metadataName, externalizable); 3684 } 3685 } 3686 3687 /** 3688 * Synchronize a double metadata from a field. 3689 * @param metadata the metadata. 3690 * @param form the form containing the field. 3691 * @param metadataDefinition the metadata definition. 3692 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3693 * @throws WorkflowException if an error occurs. 3694 */ 3695 protected void _synchronizeDoubleMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3696 { 3697 String metadataName = metadataDefinition.getName(); 3698 SimpleField<Double> field = form.getDoubleArray(metadataName); 3699 3700 if (field != null && field.getValues() != null && (metadataDefinition.isMultiple() || (field.getValues().length > 0 && field.getValues()[0] != null))) 3701 { 3702 if (metadataDefinition.isMultiple()) 3703 { 3704 _synchronizeMultipleDoubleMetadata(metadata, form, externalizable, metadataName, field); 3705 } 3706 else 3707 { 3708 if (field.getMode() == MODE.REMOVE) 3709 { 3710 metadata.removeMetadata(metadataName); 3711 } 3712 else 3713 { 3714 _setMetadata(metadata, metadataName, form, field.getValues()[0], externalizable); 3715 } 3716 } 3717 } 3718 else 3719 { 3720 if (externalizable) 3721 { 3722 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3723 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 3724 } 3725 3726 _removeMetadataIfExists(metadata, metadataName, externalizable); 3727 } 3728 } 3729 3730 private void _synchronizeMultipleDoubleMetadata(ModifiableCompositeMetadata metadata, Form form, boolean externalizable, String metadataName, SimpleField<Double> field) 3731 { 3732 if (field.getMode() == MODE.REPLACE 3733 || field.getMode() == MODE.INSERT && !metadata.hasMetadata(metadataName)) 3734 { 3735 _setMetadata(metadata, metadataName, form, field.getValues(), externalizable); 3736 } 3737 else if (field.getMode() != MODE.REMOVE || metadata.hasMetadata(metadataName)) 3738 { 3739 Double[] array = ArrayUtils.toObject(metadata.getDoubleArray(metadataName)); 3740 if (field.getMode() == MODE.INSERT) 3741 { 3742 array = ArrayUtils.addAll(array, field.getValues()); 3743 } 3744 else 3745 { 3746 array = ArrayUtils.removeElements(array, field.getValues()); 3747 } 3748 3749 if (array.length > 0) 3750 { 3751 _setMetadata(metadata, metadataName, form, array, externalizable); 3752 } 3753 else 3754 { 3755 _removeMetadataIfExists(metadata, metadataName, externalizable); 3756 } 3757 } 3758 } 3759 3760 /** 3761 * Synchronize a boolean metadata from a field. 3762 * @param metadata the metadata. 3763 * @param form the form containing the field. 3764 * @param metadataDefinition the metadata definition. 3765 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3766 * @throws WorkflowException if an error occurs. 3767 */ 3768 protected void _synchronizeBooleanMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) throws WorkflowException 3769 { 3770 String metadataName = metadataDefinition.getName(); 3771 SimpleField<Boolean> field = form.getBooleanArray(metadataName); 3772 3773 if (field != null && field.getValues() != null && (metadataDefinition.isMultiple() || (field.getValues().length > 0 && field.getValues()[0] != null))) 3774 { 3775 if (metadataDefinition.isMultiple()) 3776 { 3777 _synchronizeMultipleBooleanMetadata(metadata, form, externalizable, metadataName, field); 3778 } 3779 else 3780 { 3781 if (field.getMode() == MODE.REMOVE) 3782 { 3783 metadata.removeMetadata(metadataName); 3784 } 3785 else 3786 { 3787 _setMetadata(metadata, metadataName, form, field.getValues()[0], externalizable); 3788 } 3789 } 3790 } 3791 else 3792 { 3793 if (externalizable) 3794 { 3795 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3796 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 3797 } 3798 3799 _removeMetadataIfExists(metadata, metadataName, externalizable); 3800 } 3801 } 3802 3803 private void _synchronizeMultipleBooleanMetadata(ModifiableCompositeMetadata metadata, Form form, boolean externalizable, String metadataName, SimpleField<Boolean> field) 3804 { 3805 if (field.getMode() == MODE.REPLACE 3806 || field.getMode() == MODE.INSERT && !metadata.hasMetadata(metadataName)) 3807 { 3808 _setMetadata(metadata, metadataName, form, field.getValues(), externalizable); 3809 } 3810 else if (field.getMode() != MODE.REMOVE || metadata.hasMetadata(metadataName)) 3811 { 3812 Boolean[] array = ArrayUtils.toObject(metadata.getBooleanArray(metadataName)); 3813 if (field.getMode() == MODE.INSERT) 3814 { 3815 array = ArrayUtils.addAll(array, field.getValues()); 3816 } 3817 else 3818 { 3819 array = ArrayUtils.removeElements(array, field.getValues()); 3820 } 3821 3822 if (array.length > 0) 3823 { 3824 _setMetadata(metadata, metadataName, form, array, externalizable); 3825 } 3826 else 3827 { 3828 _removeMetadataIfExists(metadata, metadataName, externalizable); 3829 } 3830 } 3831 } 3832 3833 /** 3834 * Synchronize a binary metadata from a field. 3835 * @param metadata the metadata. 3836 * @param form the form containing the field. 3837 * @param allErrors the errors. 3838 * @param user the user. 3839 * @param metadataDefinition the metadata definition. 3840 * @param metadataPath the current metadata path. 3841 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3842 * @throws WorkflowException if an error occurs. 3843 */ 3844 protected void _synchronizeBinaryMetadata(ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, boolean externalizable) throws WorkflowException 3845 { 3846 String metadataName = metadataDefinition.getName(); 3847 BinaryField field = form.getBinaryField(metadataName); 3848 3849 if (field != null && field.getValues() != null) 3850 { 3851 String uploadId = field.getValues()[0]; 3852 3853 if (field.hasBinaryValue()) 3854 { 3855 // Set the value from an existing metadata. 3856 BinaryMetadata binaryValue = field.getBinaryValue(); 3857 3858 ModifiableBinaryMetadata binaryMetadata = null; 3859 if (externalizable) 3860 { 3861 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3862 binaryMetadata = ExternalizableMetadataHelper.setLocalBinaryMetadata(metadata, metadataName, status); 3863 } 3864 else 3865 { 3866 binaryMetadata = ExternalizableMetadataHelper.getBinaryMetadata(metadata, metadataName); 3867 } 3868 3869 binaryMetadata.setFilename(binaryValue.getFilename()); 3870 binaryMetadata.setMimeType(binaryValue.getMimeType()); 3871 binaryMetadata.setEncoding(binaryValue.getEncoding()); 3872 binaryMetadata.setLastModified(binaryValue.getLastModified()); 3873 binaryMetadata.setInputStream(binaryValue.getInputStream()); 3874 } 3875 else if (!uploadId.equals(UNTOUCHED_BINARY)) 3876 { 3877 try 3878 { 3879 Upload upload = _uploadManager.getUpload(user, uploadId); 3880 ModifiableBinaryMetadata binaryMetadata = null; 3881 if (externalizable) 3882 { 3883 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3884 binaryMetadata = ExternalizableMetadataHelper.setLocalBinaryMetadata(metadata, metadataName, status); 3885 } 3886 else 3887 { 3888 binaryMetadata = ExternalizableMetadataHelper.getBinaryMetadata(metadata, metadataName); 3889 } 3890 3891 binaryMetadata.setFilename(upload.getFilename()); 3892 binaryMetadata.setMimeType(upload.getMimeType()); 3893 binaryMetadata.setLastModified(upload.getUploadedDate()); 3894 binaryMetadata.setInputStream(upload.getInputStream()); 3895 } 3896 catch (NoSuchElementException e) 3897 { 3898 Errors errors = new Errors(); 3899 3900 List<String> parameters = new ArrayList<>(); 3901 parameters.add(uploadId); 3902 parameters.add(metadataPath); 3903 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_UPLOAD_MISSING", parameters)); 3904 allErrors.addError(metadataPath, errors); 3905 } 3906 } 3907 } 3908 else 3909 { 3910 if (externalizable) 3911 { 3912 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3913 ExternalizableMetadataHelper.updateBinaryMetadataStatus(metadata, metadataName, status); 3914 } 3915 3916 _removeMetadataIfExists(metadata, metadataName, externalizable); 3917 } 3918 } 3919 3920 /** 3921 * Synchronize a file metadata from a field. 3922 * @param metadata the metadata. 3923 * @param form the form containing the field. 3924 * @param allErrors the errors. 3925 * @param user the user. 3926 * @param metadataDefinition the metadata definition. 3927 * @param metadataPath the current metadata path. 3928 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 3929 * @throws WorkflowException if an error occurs. 3930 */ 3931 protected void _synchronizeFileMetadata(ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, boolean externalizable) throws WorkflowException 3932 { 3933 String metadataName = metadataDefinition.getName(); 3934 SimpleField<String> field = form.getStringArray(metadataName); 3935 3936 if (field != null && field.getValues() != null) 3937 { 3938 String fileType = form.getStringArray(metadataName + "#type").getValues()[0]; 3939 String localMetadataName = metadataName; 3940 if (externalizable) 3941 { 3942 localMetadataName = ExternalizableMetadataHelper.getMetadataName(metadata, metadataName, ExternalizableMetadataStatus.LOCAL); 3943 } 3944 3945 if (METADATA_FILE.equals(fileType)) 3946 { 3947 try 3948 { 3949 // Remove string metadata if exists 3950 metadata.getString(localMetadataName); 3951 metadata.removeMetadata(localMetadataName); 3952 } 3953 catch (UnknownMetadataException e) 3954 { 3955 // Do nothing 3956 } 3957 3958 _synchronizeBinaryMetadata(metadata, form, allErrors, user, metadataDefinition, metadataPath, externalizable); 3959 } 3960 else if (EXPLORER_FILE.equals(fileType)) 3961 { 3962 try 3963 { 3964 metadata.getBinaryMetadata(localMetadataName, false); 3965 metadata.removeMetadata(localMetadataName); 3966 } 3967 catch (UnknownMetadataException e) 3968 { 3969 // Do nothing 3970 } 3971 3972 _synchronizeStringMetadata(metadata, form, metadataDefinition, externalizable); 3973 } 3974 } 3975 else 3976 { 3977 if (externalizable) 3978 { 3979 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 3980 String localMetadataName = ExternalizableMetadataHelper.getMetadataName(metadata, metadataName, ExternalizableMetadataStatus.LOCAL); 3981 try 3982 { 3983 metadata.getBinaryMetadata(localMetadataName, false); 3984 ExternalizableMetadataHelper.updateBinaryMetadataStatus(metadata, metadataName, status); 3985 } 3986 catch (UnknownMetadataException e) 3987 { 3988 // Do nothing 3989 } 3990 3991 try 3992 { 3993 metadata.getString(localMetadataName); 3994 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 3995 } 3996 catch (UnknownMetadataException e) 3997 { 3998 // Do nothing 3999 } 4000 } 4001 4002 _removeMetadataIfExists(metadata, metadataName, externalizable); 4003 } 4004 } 4005 4006 /** 4007 * Prepare to synchronize a content reference metadata from a field. 4008 * @param content the content. 4009 * @param metadata the metadata. 4010 * @param form the form containing the field. 4011 * @param allErrors the errors. 4012 * @param user the user. 4013 * @param metadataDefinition the metadata definition. 4014 * @param metadataPath the current metadata path. 4015 * @param invertEditActionId The action id for editing invert relation 4016 * @throws AmetysRepositoryException If an error occurs. 4017 * @throws WorkflowException if an error occurs. 4018 */ 4019 protected void _prepareSynchronizeContentReferenceMetadata (Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId) throws AmetysRepositoryException, WorkflowException 4020 { 4021 try 4022 { 4023 if (_invertRelationEnabled() && StringUtils.isNotEmpty(metadataDefinition.getInvertRelationPath())) 4024 { 4025 String metadataName = metadataDefinition.getName(); 4026 SimpleField<Content> field = form.getContentArray(metadataName); 4027 4028 if (field != null && field.getValues() != null) 4029 { 4030 Content[] values = field.getValues(); 4031 4032 String[] contentIds = new String[values.length]; 4033 for (int i = 0; i < values.length; i++) 4034 { 4035 contentIds[i] = values[i].getId(); 4036 } 4037 4038 String[] oldValues = metadata.getStringArray(metadataName, new String[0]); 4039 String[] newRefValues = new String[0]; 4040 String[] toRemoveValues = new String[0]; 4041 4042 if (field.getMode() == MODE.REPLACE) 4043 { 4044 newRefValues = ArrayUtils.removeElements(contentIds, oldValues); 4045 toRemoveValues = ArrayUtils.removeElements(oldValues, contentIds); 4046 } 4047 else if (field.getMode() == MODE.INSERT) 4048 { 4049 newRefValues = ArrayUtils.removeElements(contentIds, oldValues); 4050 4051 if (!metadataDefinition.isMultiple()) 4052 { 4053 toRemoveValues = oldValues; // the old value should be removed 4054 } 4055 } 4056 else // (field.getMode() == MODE.REMOVE) 4057 { 4058 toRemoveValues = contentIds; 4059 } 4060 4061 // New mutual references which will be added 4062 _lockNewReferencedContent(content, allErrors, user, metadataDefinition, metadataPath, invertEditActionId, newRefValues); 4063 4064 // Old mutual references which will be removed 4065 _lockOldReferencedThatWillBeRemovedContent(allErrors, user, metadataDefinition, metadataPath, invertEditActionId, toRemoveValues); 4066 } 4067 else if (metadata.hasMetadata(metadataName)) 4068 { 4069 _lockOldReferencedContent(metadata, allErrors, user, metadataDefinition, metadataPath, invertEditActionId, metadataName); 4070 } 4071 } 4072 } 4073 catch (RepositoryException e) 4074 { 4075 throw new WorkflowException("Error preparing to synchronize content references for content " + content.getId() + " and metadata at path '" + metadataPath + "'.", e); 4076 } 4077 } 4078 4079 private void _lockNewReferencedContent(Content content, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, String[] newValues) throws RepositoryException 4080 { 4081 for (String refContentId : newValues) 4082 { 4083 Content refContent = _resolver.resolveById(refContentId); 4084 if (_needTriggerEditWorkflowAction((ModifiableContent) refContent, metadataDefinition.getContentType(), metadataDefinition.getInvertRelationPath(), content.getId())) 4085 { 4086 // Check if edit action in available on referenced contents 4087 if (_isEditRefContentAvailable(invertEditActionId, refContent, metadataDefinition.getForceInvert(), metadataPath, user, allErrors)) 4088 { 4089 if (refContent instanceof LockableAmetysObject && !((LockableAmetysObject) refContent).isLocked()) 4090 { 4091 // Get lock on referenced content 4092 ((LockableAmetysObject) refContent).lock(); 4093 } 4094 } 4095 } 4096 } 4097 } 4098 4099 private void _lockOldReferencedThatWillBeRemovedContent(AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, String[] toRemove) 4100 { 4101 for (String refContentId : toRemove) 4102 { 4103 try 4104 { 4105 Content refContent = _resolver.resolveById(refContentId); 4106 if (_isEditRefContentAvailable(invertEditActionId, refContent, metadataDefinition.getForceInvert(), metadataPath, user, allErrors)) 4107 { 4108 if (refContent instanceof LockableAmetysObject && !((LockableAmetysObject) refContent).isLocked()) 4109 { 4110 // Get lock on old referenced content 4111 ((LockableAmetysObject) refContent).lock(); 4112 } 4113 } 4114 } 4115 catch (UnknownAmetysObjectException e) 4116 { 4117 // The old referenced content does not exist anymore. Ignore it. 4118 } 4119 } 4120 } 4121 4122 private void _lockOldReferencedContent(ModifiableCompositeMetadata metadata, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, String metadataName) 4123 { 4124 String[] oldValues = metadata.getStringArray(metadataName, new String[0]); 4125 for (String refContentId : oldValues) 4126 { 4127 try 4128 { 4129 Content refContent = _resolver.resolveById(refContentId); 4130 if (_isEditRefContentAvailable(invertEditActionId, refContent, metadataDefinition.getForceInvert(), metadataPath, user, allErrors)) 4131 { 4132 if (refContent instanceof LockableAmetysObject && !((LockableAmetysObject) refContent).isLocked()) 4133 { 4134 // Get lock on old referenced content 4135 ((LockableAmetysObject) refContent).lock(); 4136 } 4137 } 4138 } 4139 catch (UnknownAmetysObjectException e) 4140 { 4141 // The old referenced content does not exist anymore. Ignore it. 4142 } 4143 } 4144 } 4145 4146 /** 4147 * Synchronize a content reference metadata from a field. 4148 * @param content the content. 4149 * @param metadata the metadata. 4150 * @param form the form containing the field. 4151 * @param allErrors the errors. 4152 * @param user the user. 4153 * @param metadataDefinition the metadata definition. 4154 * @param metadataPath the current metadata path. 4155 * @param invertEditActionId The action id for editing invert relation 4156 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 4157 * @throws WorkflowException if an error occurs. 4158 */ 4159 protected void _synchronizeContentReferenceMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, boolean externalizable) throws WorkflowException 4160 { 4161 String metadataName = metadataDefinition.getName(); 4162 SimpleField<Content> field = form.getContentArray(metadataName); 4163 String contentTypeId = metadataDefinition.getContentType(); 4164 String invert = metadataDefinition.getInvertRelationPath(); 4165 4166 if (field != null && field.getValues() != null) 4167 { 4168 Content[] values = field.getValues(); 4169 4170 if (metadataDefinition.isMultiple()) 4171 { 4172 _synchronizeMultipleContentReferenceMetadata(content, metadata, form, allErrors, field, metadataDefinition, metadataPath, invertEditActionId, contentTypeId, invert, externalizable); 4173 } 4174 else if (values.length > 0) 4175 { 4176 _synchronizeSingleContentReferenceMetadata(content, metadata, form, allErrors, field, metadataDefinition, metadataPath, invertEditActionId, contentTypeId, invert, externalizable); 4177 } 4178 else 4179 { 4180 if (externalizable) 4181 { 4182 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 4183 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 4184 } 4185 4186 _removeMetadataIfExists(metadata, metadataName, externalizable); 4187 } 4188 } 4189 else 4190 { 4191 ExternalizableMetadataStatus status = null; 4192 4193 String[] oldValues = new String[0]; 4194 if (externalizable) 4195 { 4196 status = form.getExternalizableField(metadataName).getStatus(); 4197 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 4198 4199 try 4200 { 4201 oldValues = ExternalizableMetadataHelper.getStringArray(metadata, metadataName, status); 4202 } 4203 catch (UnknownMetadataException e) 4204 { 4205 // Nothing 4206 } 4207 } 4208 else 4209 { 4210 oldValues = metadata.getStringArray(metadataName, new String[0]); 4211 } 4212 4213 // Remove metadata and JCR references 4214 _removeMetadataIfExists(metadata, metadataName, externalizable); 4215 4216 if (_invertRelationEnabled() && StringUtils.isNotEmpty(invert) && (!externalizable || ExternalizableMetadataStatus.LOCAL == form.getExternalizableField(metadataName).getStatus())) 4217 { 4218 _removeInvertRelations(content.getId(), metadataDefinition, metadataPath, oldValues, invertEditActionId, allErrors); 4219 } 4220 } 4221 } 4222 4223 private void _synchronizeMultipleContentReferenceMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, SimpleField<Content> field, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, String contentTypeId, String invert, boolean externalizable) throws WorkflowException 4224 { 4225 String metadataName = metadataDefinition.getName(); 4226 Content[] values = field.getValues(); 4227 4228 String[] contentIds = new String[values.length]; 4229 for (int i = 0; i < values.length; i++) 4230 { 4231 contentIds[i] = values[i].getId(); 4232 } 4233 4234 String localMetadataName = ExternalizableMetadataHelper.getMetadataName(metadata, metadataName, ExternalizableMetadataStatus.LOCAL); 4235 String[] oldValues = metadata.getStringArray(localMetadataName, new String[0]); 4236 String[] newValues; 4237 4238 if (field.getMode() == MODE.REPLACE 4239 || field.getMode() == MODE.INSERT && !metadata.hasMetadata(metadataName)) 4240 { 4241 newValues = contentIds; 4242 4243 // Set content ID values. 4244 _setMetadata(metadata, metadataName, form, contentIds, externalizable); 4245 } 4246 else if (field.getMode() != MODE.REMOVE || metadata.hasMetadata(metadataName)) 4247 { 4248 Set<String> newMetadataValues = new LinkedHashSet<>(); 4249 newMetadataValues.addAll(Arrays.asList(oldValues)); 4250 if (field.getMode() == MODE.INSERT) 4251 { 4252 newMetadataValues.addAll(Arrays.asList(contentIds)); 4253 } 4254 else 4255 { 4256 newMetadataValues.removeAll(Arrays.asList(contentIds)); 4257 } 4258 4259 newValues = newMetadataValues.toArray(new String[0]); 4260 4261 // Set content ID values. 4262 if (newValues.length > 0) 4263 { 4264 _setMetadata(metadata, metadataName, form, newValues, externalizable); 4265 } 4266 else 4267 { 4268 metadata.removeMetadata(metadataName); 4269 } 4270 } 4271 else 4272 { 4273 newValues = new String[0]; 4274 } 4275 4276 _synchronizeRelationOfMultipleContentReferenceMetadata(content, form, allErrors, metadataDefinition, metadataPath, invertEditActionId, contentTypeId, invert, externalizable, metadataName, values, oldValues, newValues); 4277 } 4278 4279 private void _synchronizeRelationOfMultipleContentReferenceMetadata(Content content, Form form, AllErrors allErrors, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, String contentTypeId, String invert, boolean externalizable, String metadataName, Content[] values, String[] oldValues, String[] newValues) throws WorkflowException 4280 { 4281 if (_invertRelationEnabled() && StringUtils.isNotEmpty(invert) && (!externalizable || ExternalizableMetadataStatus.LOCAL == form.getExternalizableField(metadataName).getStatus())) 4282 { 4283 // Remove old mutual references. 4284 String[] toRemove = ArrayUtils.removeElements(oldValues, newValues); 4285 _removeInvertRelations(content.getId(), metadataDefinition, metadataPath, toRemove, invertEditActionId, allErrors); 4286 4287 // Content ID? 4288 for (Content refContent : values) 4289 { 4290 if (ArrayUtils.contains(newValues, refContent.getId())) 4291 { 4292 _setInvertRelation(content, metadataPath, refContent, contentTypeId, invert, invertEditActionId, allErrors); 4293 } 4294 } 4295 } 4296 } 4297 4298 private void _synchronizeSingleContentReferenceMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, SimpleField<Content> field, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, String contentTypeId, String invert, boolean externalizable) throws WorkflowException 4299 { 4300 String metadataName = metadataDefinition.getName(); 4301 Content[] values = field.getValues(); 4302 4303 String oldValue = null; 4304 try 4305 { 4306 oldValue = externalizable ? ExternalizableMetadataHelper.getString(metadata, metadataName, ExternalizableMetadataStatus.LOCAL) : metadata.getString(metadataName, null); 4307 } 4308 catch (UnknownMetadataException e) 4309 { 4310 // nothing 4311 } 4312 4313 Content refContent = values[0]; 4314 boolean invertRequired = true; 4315 4316 if (field.getMode() == MODE.REMOVE) 4317 { 4318 if (StringUtils.equals(refContent.getId(), oldValue)) 4319 { 4320 ExternalizableMetadataStatus status = externalizable ? form.getExternalizableField(metadataName).getStatus() : null; 4321 4322 _removeMetadataIfExists(metadata, metadataName, externalizable); 4323 4324 if (externalizable) 4325 { 4326 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 4327 } 4328 } 4329 else 4330 { 4331 invertRequired = false; 4332 if (_logger.isWarnEnabled()) 4333 { 4334 _logger.warn("Cannot remove reference to content '" + refContent.getId() + "' on content '" + content.getId() + "' metadata '" + metadataDefinition.getId() + "' because it is not a current value"); 4335 } 4336 } 4337 } 4338 else 4339 { 4340 if (StringUtils.equals(refContent.getId(), oldValue)) 4341 { 4342 invertRequired = false; 4343 if (externalizable) 4344 { 4345 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 4346 ExternalizableMetadataHelper.updateStatus(metadata, metadataName, status); 4347 } 4348 } 4349 else 4350 { 4351 _setMetadata(metadata, metadataName, form, refContent.getId(), externalizable); 4352 } 4353 4354 } 4355 4356 // Do not edit the invert relation if the externalizable status is external 4357 invertRequired = invertRequired && (!externalizable || ExternalizableMetadataStatus.LOCAL == form.getExternalizableField(metadataName).getStatus()); 4358 _synchronizeRelationOfSingleContentReferenceMetadata(content, allErrors, field, metadataDefinition, metadataPath, invertEditActionId, contentTypeId, invert, oldValue, refContent, invertRequired); 4359 } 4360 4361 private void _synchronizeRelationOfSingleContentReferenceMetadata(Content content, AllErrors allErrors, SimpleField<Content> field, MetadataDefinition metadataDefinition, String metadataPath, int invertEditActionId, String contentTypeId, String invert, String oldValue, Content refContent, boolean invertRequired) throws WorkflowException 4362 { 4363 if (_invertRelationEnabled() && invertRequired && StringUtils.isNotEmpty(invert)) 4364 { 4365 if (oldValue != null) 4366 { 4367 _removeInvertRelation(content.getId(), metadataDefinition, metadataPath, oldValue, invertEditActionId, allErrors); 4368 } 4369 4370 if (field.getMode() != MODE.REMOVE) 4371 { 4372 _setInvertRelation(content, metadataPath, refContent, contentTypeId, invert, invertEditActionId, allErrors); 4373 } 4374 } 4375 } 4376 4377 /** 4378 * Synchronize a sub-content metadata from a field. 4379 * @param content The Content. 4380 * @param metadata the metadata. 4381 * @param form the form containing the field. 4382 * @param allErrors the errors. 4383 * @param user the user. 4384 * @param metadataDefinition the metadata definition. 4385 * @param metadataPath the current metadata path. 4386 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 4387 * @throws WorkflowException if an error occurs. 4388 */ 4389 protected void _synchronizeSubContentMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, boolean externalizable) throws WorkflowException 4390 { 4391 String metadataName = metadataDefinition.getName(); 4392 4393 SubContentField subContentField = form.getSubContentField(metadataName); 4394 4395 String[] values = subContentField.getContentNamesOrIds(); 4396 String[] contentTypes = subContentField.getContentTypes(); 4397 Boolean[] isNew = subContentField.getIsNew(); 4398 4399 String contentLanguage = subContentField.getContentLanguage(); 4400 int initWorkflowActionId = subContentField.getWorkflowActionId(); 4401 String workflowName = subContentField.getWorkflowName(); 4402 4403 // TODO Handle ordering. 4404 TraversableAmetysObject objectCollection = null; 4405 if (externalizable) 4406 { 4407 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 4408 objectCollection = ExternalizableMetadataHelper.getObjectCollection(metadata, metadataName, status); 4409 } 4410 else 4411 { 4412 objectCollection = ExternalizableMetadataHelper.getObjectCollection(metadata, metadataName); 4413 } 4414 4415 4416 Map<String, ModifiableContent> contentsToDelete = new HashMap<>(); 4417 4418 if (subContentField.getMode() == MODE.REPLACE) 4419 { 4420 try (AmetysObjectIterable<Content> subContents = objectCollection.getChildren()) 4421 { 4422 for (Content subContent : subContents) 4423 { 4424 if (subContent instanceof ModifiableContent) 4425 { 4426 contentsToDelete.put(subContent.getId(), (ModifiableContent) subContent); 4427 } 4428 } 4429 } 4430 } 4431 else if (subContentField.getMode() == MODE.REMOVE) 4432 { 4433 for (String value : values) 4434 { 4435 ModifiableContent contentToRemove = _resolver.resolveById(value); 4436 contentsToDelete.put(contentToRemove.getId(), contentToRemove); 4437 } 4438 } 4439 4440 if (values != null && (metadataDefinition.isMultiple() || values[0] != null) && subContentField.getMode() != MODE.REMOVE) 4441 { 4442 try 4443 { 4444 for (int i = 0; i < values.length; i++) 4445 { 4446 if (isNew[i]) 4447 { 4448 // New content: create it. 4449 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(); 4450 Map<String, Object> inputs = _getInputsForSubContentCreation(content, metadata, form, user, metadataDefinition, metadataPath, values[i], new String[] {contentTypes[i]}, contentLanguage); 4451 workflow.initialize(workflowName, initWorkflowActionId, inputs); 4452 } 4453 else 4454 { 4455 // The content is still there: remove it from the list of contents to delete. 4456 String contentId = values[i]; 4457 contentsToDelete.remove(contentId); 4458 } 4459 } 4460 4461 } 4462 catch (WorkflowException e) 4463 { 4464 Errors errors = new Errors(); 4465 4466 List<String> parameters = new ArrayList<>(); 4467 parameters.add(metadataPath); 4468 parameters.add(content.getId()); 4469 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_SUBCONTENT_CREATION", parameters)); 4470 allErrors.addError(metadataPath, errors); 4471 } 4472 } 4473 4474 // Delete contents. 4475 for (ModifiableContent contentToDelete : contentsToDelete.values()) 4476 { 4477 contentToDelete.remove(); 4478 } 4479 } 4480 4481 4482 /** 4483 * Provide the inputs to use for the creation of a subcontent. 4484 * @param content The parent content 4485 * @param metadata the metadata. 4486 * @param form the form containing the field. 4487 * @param user the user. 4488 * @param metadataDefinition the metadata definition. 4489 * @param metadataPath the current metadata path. 4490 * @param name The name of the subcontent to create 4491 * @param cTypes Content types array of the subcontent to create 4492 * @param language The language of the subcontent to create 4493 * @return the map of inputs 4494 */ 4495 protected Map<String, Object> _getInputsForSubContentCreation(Content content, ModifiableCompositeMetadata metadata, Form form, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, String name, String[] cTypes, String language) 4496 { 4497 Map<String, Object> inputs = new HashMap<>(); 4498 Map<String, Object> result = new HashMap<>(); 4499 4500 inputs.put(CreateContentFunction.CONTENT_NAME_KEY, name); 4501 inputs.put(CreateContentFunction.CONTENT_TITLE_KEY, name); 4502 inputs.put(CreateContentFunction.CONTENT_TYPES_KEY, cTypes); 4503 inputs.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, language); 4504 inputs.put(CreateContentFunction.PARENT_CONTENT_ID_KEY, content.getId()); 4505 inputs.put(CreateContentFunction.PARENT_CONTENT_METADATA_PATH_KEY, metadataPath); 4506 4507 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, result); 4508 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 4509 4510 return inputs; 4511 } 4512 4513 4514 /** 4515 * Synchronize a sub-content metadata from a field. 4516 * @param metadata the metadata. 4517 * @param form the form containing the field. 4518 * @param metadataDefinition the metadata definition. 4519 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 4520 */ 4521 protected void _synchronizeReferenceMetadata(ModifiableCompositeMetadata metadata, Form form, MetadataDefinition metadataDefinition, boolean externalizable) 4522 { 4523 String metadataName = metadataDefinition.getName(); 4524 ReferenceField field = form.getReferenceField(metadataName); 4525 4526 if (field != null && field.getValue() != null && field.getMode() != MODE.REMOVE) 4527 { 4528 ModifiableCompositeMetadata refMetadata = null; 4529 if (externalizable) 4530 { 4531 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 4532 refMetadata = ExternalizableMetadataHelper.getCompositeMetadata(metadata, metadataName, status, true); 4533 } 4534 else 4535 { 4536 refMetadata = ExternalizableMetadataHelper.getCompositeMetadata(metadata, metadataName); 4537 } 4538 refMetadata.setMetadata("value", field.getValue()); 4539 refMetadata.setMetadata("type", field.getType()); 4540 } 4541 else 4542 { 4543 if (metadata.hasMetadata(metadataName)) 4544 { 4545 metadata.removeMetadata(metadataName); 4546 } 4547 } 4548 } 4549 4550 /** 4551 * Synchronize a rich text metadata from a field. 4552 * @param metadata the metadata. 4553 * @param form the form containing the field. 4554 * @param allErrors the errors. 4555 * @param user the user. 4556 * @param metadataDefinition the metadata definition. 4557 * @param metadataPath the current metadata path. 4558 * @param externalizable <code>true</code> if the metadata is externalizable (local and external value) 4559 * @throws WorkflowException if an error occurs. 4560 */ 4561 protected void _synchronizeRichTextMetadata(ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, UserIdentity user, MetadataDefinition metadataDefinition, String metadataPath, boolean externalizable) throws WorkflowException 4562 { 4563 String metadataName = metadataDefinition.getName(); 4564 RichTextField richTextField = form.getRichTextField(metadataName); 4565 4566 if (richTextField != null && richTextField.getContent() != null) 4567 { 4568 String format = richTextField.getFormat(); 4569 4570 ModifiableCompositeMetadata metadataHolder = _getMetadataHolder(metadata, metadataName); 4571 ModifiableRichText richText = null; 4572 if (externalizable) 4573 { 4574 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 4575 richText = ExternalizableMetadataHelper.setLocalRichTextMetadata(metadataHolder, metadataName, status); 4576 } 4577 else 4578 { 4579 richText = ExternalizableMetadataHelper.getRichTextMetadata(metadataHolder, metadataName); 4580 } 4581 4582 if ("docbook".equals(format)) 4583 { 4584 _copyRichText(richTextField, richText, metadataDefinition, metadataPath, allErrors); 4585 } 4586 else 4587 { 4588 _transformRichText(richTextField, richText, metadataDefinition, metadataPath, allErrors); 4589 } 4590 } 4591 else 4592 { 4593 if (externalizable) 4594 { 4595 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 4596 ExternalizableMetadataHelper.updateRichTextMetadataStatus(metadata, metadataName, status); 4597 } 4598 4599 _removeMetadataIfExists(metadata, metadataName, externalizable); 4600 } 4601 } 4602 4603 /** 4604 * Set the rich text metadata from a source that is not docbook. 4605 * @param field the RichText form field. 4606 * @param richText the rich text metadata to populate. 4607 * @param metadataDefinition the metadata definition. 4608 * @param metadataPath the metadata path. 4609 * @param allErrors the error list. 4610 */ 4611 protected void _transformRichText(RichTextField field, ModifiableRichText richText, MetadataDefinition metadataDefinition, String metadataPath, AllErrors allErrors) 4612 { 4613 try 4614 { 4615 RichTextTransformer richTextTransformer = metadataDefinition.getRichTextTransformer(); 4616 String content = field.getContent(); 4617 4618 richTextTransformer.transform(content, richText); 4619 } 4620 catch (IOException e) 4621 { 4622 if (_logger.isErrorEnabled()) 4623 { 4624 _logger.error("Unable to transform rich text", e); 4625 } 4626 4627 Errors errors = new Errors(); 4628 List<String> parameters = new ArrayList<>(); 4629 parameters.add(e.getMessage()); 4630 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_RICHTEXT_TRANSFORM", parameters)); 4631 allErrors.addError(metadataPath, errors); 4632 } 4633 } 4634 4635 /** 4636 * Set a rich text metadata from an existing rich text metadata. 4637 * @param field the RichText form field. 4638 * @param richText the rich text metadata to populate. 4639 * @param metadataDefinition the metadata definition. 4640 * @param metadataPath the metadata path. 4641 * @param allErrors the error list. 4642 */ 4643 protected void _copyRichText(RichTextField field, ModifiableRichText richText, MetadataDefinition metadataDefinition, String metadataPath, AllErrors allErrors) 4644 { 4645 try 4646 { 4647 String content = field.getContent(); 4648 4649 ByteArrayInputStream is = new ByteArrayInputStream(content.getBytes("UTF-8")); 4650 richText.setEncoding("UTF-8"); 4651 richText.setMimeType("application/xml"); 4652 richText.setLastModified(new Date()); 4653 richText.setInputStream(is); 4654 4655 ModifiableFolder dataFolder = richText.getAdditionalDataFolder(); 4656 Map<String, Resource> dataToCopy = field.getAdditionalData(); 4657 if (dataToCopy != null) 4658 { 4659 for (String fileName : dataToCopy.keySet()) 4660 { 4661 Resource sourceFile = dataToCopy.get(fileName); 4662 4663 if (dataFolder.hasFile(fileName)) 4664 { 4665 dataFolder.remove(fileName); 4666 } 4667 4668 ModifiableResource destFile = dataFolder.addFile(fileName).getResource(); 4669 destFile.setEncoding(sourceFile.getEncoding()); 4670 destFile.setMimeType(sourceFile.getMimeType()); 4671 destFile.setLastModified(sourceFile.getLastModified()); 4672 destFile.setInputStream(sourceFile.getInputStream()); 4673 } 4674 } 4675 } 4676 catch (UnsupportedEncodingException e) 4677 { 4678 // Ignore 4679 } 4680 } 4681 4682 /** 4683 * Determines if the edit workflow action will be trigger on this referenced content 4684 * @param refContent The referenced content 4685 * @param refContentTypeId The content type 4686 * @param invertRelationPath The path of invert relationship 4687 * @param currentContentId The content being editing 4688 * @return true if the edit workflow action will occur 4689 * @throws RepositoryException if an error occurred 4690 */ 4691 protected boolean _needTriggerEditWorkflowAction (ModifiableContent refContent, String refContentTypeId, String invertRelationPath, String currentContentId) throws RepositoryException 4692 { 4693 ContentType refContentType = _contentTypeExtensionPoint.getExtension(refContentTypeId); 4694 MetadataDefinition invertMetadataDef = refContentType.getMetadataDefinitionByPath(invertRelationPath); 4695 4696 ModifiableCompositeMetadata refContentMeta = _getRelationMetaHolder(refContent, refContentType, invertRelationPath, currentContentId, false); 4697 if (refContentMeta == null) 4698 { 4699 return true; 4700 } 4701 4702 int lastSlashIndex = invertRelationPath.lastIndexOf(ContentConstants.METADATA_PATH_SEPARATOR); 4703 String metaName = lastSlashIndex > -1 ? invertRelationPath.substring(lastSlashIndex + 1) : invertRelationPath; 4704 4705 if (invertMetadataDef.isMultiple()) 4706 { 4707 String[] values = refContentMeta.getStringArray(metaName, new String[0]); 4708 if (!ArrayUtils.contains(values, currentContentId)) 4709 { 4710 return true; 4711 } 4712 return false; 4713 } 4714 else 4715 { 4716 return !currentContentId.equals(refContentMeta.getString(metaName, null)); 4717 } 4718 } 4719 4720 /** 4721 * Set a mutual relation. 4722 * @param content the content being modified. 4723 * @param currentMetadataPath the metadata path on the content being modified. 4724 * @param refContent the content being referenced. 4725 * @param refContentTypeId the content type of the content being referenced. 4726 * @param invertRelationPath the path of the metadata to set on the content being referenced, separated by '/' 4727 * @param editActionId The current 'edit content' action ID. 4728 * @param allErrors the errors. 4729 * @throws WorkflowException if a fatal error occurs. 4730 */ 4731 protected void _setInvertRelation(Content content, String currentMetadataPath, Content refContent, String refContentTypeId, String invertRelationPath, int editActionId, AllErrors allErrors) throws WorkflowException 4732 { 4733 try 4734 { 4735 boolean needSave = false; 4736 if (content instanceof ModifiableContent && invertRelationPath != null) 4737 { 4738 String currentContentId = content.getId(); 4739 ModifiableContent modifiableRefContent = (ModifiableContent) refContent; 4740 4741 ContentType refContentType = _contentTypeExtensionPoint.getExtension(refContentTypeId); 4742 MetadataDefinition invertMetadataDef = refContentType.getMetadataDefinitionByPath(invertRelationPath); 4743 4744 Set<String> refExternalAndLocalMetadata = _externalizableMetadataProviderEP.getExternalAndLocalMetadata(modifiableRefContent); 4745 boolean externalizable = refExternalAndLocalMetadata.contains(invertRelationPath); 4746 4747 ModifiableCompositeMetadata refContentMeta = _getRelationMetaHolder(modifiableRefContent, refContentType, invertRelationPath, currentContentId, true); 4748 4749 int lastSlashIndex = invertRelationPath.lastIndexOf(ContentConstants.METADATA_PATH_SEPARATOR); 4750 String metaName = lastSlashIndex > -1 ? invertRelationPath.substring(lastSlashIndex + 1) : invertRelationPath; 4751 4752 if (invertMetadataDef.isMultiple()) 4753 { 4754 // Get the current values and add this one. 4755 String[] values = refContentMeta.getStringArray(metaName, new String[0]); 4756 if (!ArrayUtils.contains(values, currentContentId)) 4757 { 4758 String[] newValues = ArrayUtils.add(values, currentContentId); 4759 4760 if (externalizable) 4761 { 4762 // Force status to local 4763 ExternalizableMetadataHelper.setLocalMetadata(refContentMeta, metaName, newValues, ExternalizableMetadataStatus.LOCAL); 4764 } 4765 else 4766 { 4767 ExternalizableMetadataHelper.setMetadata(refContentMeta, metaName, newValues); 4768 } 4769 needSave = true; 4770 } 4771 } 4772 else 4773 { 4774 // Remove the invert relation if it exists. 4775 String oldContentId = refContentMeta.getString(metaName, null); 4776 if (oldContentId != null && !currentContentId.equals(oldContentId)) 4777 { 4778 _removeInvertRelation(refContent.getId(), invertMetadataDef, invertRelationPath, oldContentId, editActionId, allErrors); 4779 } 4780 4781 if (externalizable) 4782 { 4783 // Force status to local 4784 ExternalizableMetadataHelper.setLocalMetadata(refContentMeta, metaName, currentContentId, ExternalizableMetadataStatus.LOCAL); 4785 } 4786 else 4787 { 4788 ExternalizableMetadataHelper.setMetadata(refContentMeta, metaName, currentContentId); 4789 } 4790 needSave = true; 4791 } 4792 4793 if (needSave) 4794 { 4795 modifiableRefContent.saveChanges(); 4796 4797 // Trigger edit workflow action on the referenced content if needed. 4798 _triggerEditWorkflowAction(modifiableRefContent, editActionId); 4799 } 4800 } 4801 } 4802 catch (RepositoryException e) 4803 { 4804 _logger.error(String.format("Content reference validation error for content '%s', id '%s'", _contentHelper.getTitle(refContent), refContent.getId()), e); 4805 4806 Errors errors = new Errors(); 4807 Map<String, I18nizableText> params = new HashMap<>(); 4808 params.put("content", new I18nizableText(_contentHelper.getTitle(refContent))); 4809 params.put("error", new I18nizableText(e.getLocalizedMessage())); 4810 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_MUTUALRELATION_CREATE", params)); 4811 allErrors.addError(currentMetadataPath, errors); 4812 } 4813 } 4814 4815 /** 4816 * Check the lock status of the node 4817 * @param node The node 4818 * @throws RepositoryException If an error occurred while manipulating the node 4819 */ 4820 protected void _checkLock(Node node) throws RepositoryException 4821 { 4822 if (!_lockAlreadyChecked.contains(node.getIdentifier()) && node.isLocked()) 4823 { 4824 LockManager lockManager = node.getSession().getWorkspace().getLockManager(); 4825 4826 Lock lock = lockManager.getLock(node.getPath()); 4827 Node lockHolder = lock.getNode(); 4828 4829 lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); 4830 _lockAlreadyChecked.add(node.getIdentifier()); 4831 } 4832 } 4833 4834 /** 4835 * Checks if the "edit content" workflow action is available on a referenced content 4836 * @param editActionId The id of edit workflow action 4837 * @param refContent the referenced content to edit 4838 * @param forceInvert Force the invert edition regardless of the user's rights 4839 * @param currentMetadataPath the path of the metadata responsible for the invert relation 4840 * @param user the current user 4841 * @param allErrors the errors 4842 * @return <code>true</code> if the edit action is avalailable 4843 */ 4844 protected boolean _isEditRefContentAvailable (int editActionId, Content refContent, boolean forceInvert, String currentMetadataPath, UserIdentity user, AllErrors allErrors) 4845 { 4846 if (refContent instanceof WorkflowAwareContent) 4847 { 4848 Map<String, Object> inputs = new HashMap<>(); 4849 if (forceInvert) 4850 { 4851 // do not check user's right 4852 inputs.put(CheckRightsCondition.FORCE, true); 4853 } 4854 4855 int[] availableActions = _workflowHelper.getAvailableActions((WorkflowAwareContent) refContent, inputs); 4856 if (!ArrayUtils.contains(availableActions, editActionId)) 4857 { 4858 Errors errors = new Errors(); 4859 Map<String, I18nizableText> params = new HashMap<>(); 4860 4861 // Check lock 4862 if (refContent instanceof LockableAmetysObject) 4863 { 4864 LockableAmetysObject lockableContent = (LockableAmetysObject) refContent; 4865 if (lockableContent.isLocked() && !LockHelper.isLockOwner(lockableContent, user)) 4866 { 4867 User lockOwner = _userManager.getUser(lockableContent.getLockOwner().getPopulationId(), lockableContent.getLockOwner().getLogin()); 4868 4869 params.put("content", new I18nizableText(_contentHelper.getTitle(refContent))); 4870 params.put("lockOwner", new I18nizableText(lockOwner != null ? lockOwner.getFullName() + " (" + lockOwner.getIdentity().getLogin() + ")" : lockableContent.getLockOwner().getLogin())); 4871 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_MUTUALRELATION_REFERENCED_CONTENT_LOCKED", params)); 4872 allErrors.addError(currentMetadataPath, errors); 4873 4874 return false; 4875 } 4876 } 4877 4878 // Action in unavailable 4879 params.put("content", new I18nizableText(_contentHelper.getTitle(refContent))); 4880 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_MUTUALRELATION_UNAVAILABLE_ACTION", params)); 4881 allErrors.addError(currentMetadataPath, errors); 4882 4883 return false; 4884 } 4885 else 4886 { 4887 return true; 4888 } 4889 } 4890 else 4891 { 4892 Errors errors = new Errors(); 4893 Map<String, I18nizableText> params = new HashMap<>(); 4894 params.put("content", new I18nizableText(_contentHelper.getTitle(refContent))); 4895 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_MUTUALRELATION_NOWORKFLOWAWARE_CONTENT", params)); 4896 allErrors.addError(currentMetadataPath, errors); 4897 return false; 4898 } 4899 4900 } 4901 4902 /** 4903 * Remove a mutual relation. 4904 * @param currentContentId the ID of the content being modified. 4905 * @param metaDef the metadata definition. 4906 * @param metaPath the metadata path. 4907 * @param refContentId the ID of the content being referenced. 4908 * @param editActionId The current 'edit content' action ID. 4909 * @param allErrors the errors. 4910 * @throws WorkflowException if a fatal error occurs. 4911 */ 4912 protected void _removeInvertRelation(String currentContentId, MetadataDefinition metaDef, String metaPath, String refContentId, int editActionId, AllErrors allErrors) throws WorkflowException 4913 { 4914 try 4915 { 4916 String refContentTypeId = metaDef.getContentType(); 4917 String refMetadataPath = metaDef.getInvertRelationPath(); 4918 4919 Content refContent = _resolver.resolveById(refContentId); 4920 4921 if (refContent instanceof ModifiableContent && refMetadataPath != null) 4922 { 4923 ModifiableContent refModifiableContent = (ModifiableContent) refContent; 4924 4925 ContentType refContentType = _contentTypeExtensionPoint.getExtension(refContentTypeId); 4926 MetadataDefinition refMetadataDef = refContentType.getMetadataDefinitionByPath(refMetadataPath); 4927 4928 int lastSlashIndex = refMetadataPath.lastIndexOf(ContentConstants.METADATA_PATH_SEPARATOR); 4929 String refMetaName = lastSlashIndex > -1 ? refMetadataPath.substring(lastSlashIndex + 1) : refMetadataPath; 4930 4931 try 4932 { 4933 boolean needSave = true; 4934 ModifiableCompositeMetadata refContentMeta = _getRelationMetaHolder(refModifiableContent, refContentType, refMetadataPath, currentContentId, false); 4935 4936 if (refContentMeta != null) 4937 { 4938 Set<String> refExternalAndLocalMetadata = _externalizableMetadataProviderEP.getExternalAndLocalMetadata(refModifiableContent); 4939 boolean externalizable = refExternalAndLocalMetadata.contains(refMetadataPath); 4940 4941 if (refMetadataDef.isMultiple()) 4942 { 4943 String[] values = refContentMeta.getStringArray(refMetaName, new String[0]); 4944 int index = ArrayUtils.indexOf(values, currentContentId); 4945 if (index > -1) 4946 { 4947 String[] newValues = ArrayUtils.remove(values, index); 4948 4949 if (externalizable) 4950 { 4951 ExternalizableMetadataHelper.setLocalMetadata(refContentMeta, refMetaName, newValues, ExternalizableMetadataStatus.LOCAL); 4952 } 4953 else 4954 { 4955 ExternalizableMetadataHelper.setMetadata(refContentMeta, refMetaName, newValues); 4956 } 4957 needSave = true; 4958 } 4959 } 4960 else 4961 { 4962 if (ExternalizableMetadataHelper.removeLocalMetadataIfExists(refContentMeta, refMetaName)) 4963 { 4964 needSave = true; 4965 } 4966 } 4967 4968 if (needSave) 4969 { 4970 refModifiableContent.saveChanges(); 4971 4972 // Trigger edit workflow action on the referenced content if needed. 4973 _triggerEditWorkflowAction(refModifiableContent, editActionId); 4974 } 4975 } 4976 } 4977 catch (RepositoryException e) 4978 { 4979 Errors errors = new Errors(); 4980 Map<String, I18nizableText> params = new HashMap<>(); 4981 params.put("content", new I18nizableText(_contentHelper.getTitle(refContent))); 4982 params.put("error", new I18nizableText(e.getLocalizedMessage())); 4983 errors.addError(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_MUTUALRELATION_REMOVE", params)); 4984 allErrors.addError(metaPath, errors); 4985 } 4986 } 4987 } 4988 catch (UnknownAmetysObjectException e) 4989 { 4990 // Ignore. 4991 } 4992 } 4993 4994 /** 4995 * Remove a mutual relation. 4996 * @param currentContentId the ID of the content being modified. 4997 * @param metaDef the metadata definition. 4998 * @param metaPath the metadata path. 4999 * @param refContentIds the ID of the contents being referenced. 5000 * @param editActionId The current 'edit content' action ID. 5001 * @param allErrors the errors. 5002 * @throws WorkflowException if a fatal error occurs. 5003 */ 5004 protected void _removeInvertRelations(String currentContentId, MetadataDefinition metaDef, String metaPath, String[] refContentIds, int editActionId, AllErrors allErrors) throws WorkflowException 5005 { 5006 for (String refContentId : refContentIds) 5007 { 5008 _removeInvertRelation(currentContentId, metaDef, metaPath, refContentId, editActionId, allErrors); 5009 } 5010 } 5011 5012 /** 5013 * Get the metadata holder of the mutual relation. 5014 * @param refContent the content being referenced. 5015 * @param refContentType the content type of the content being referenced. 5016 * @param metadataPath the metadata path on the content being referenced, separated by '/' 5017 * @param searchContentId the ID of the content being modified. 5018 * @param create true to create non-existing composites and repeater entries. 5019 * @return The metadata holder of the relation, on the content being referenced.<br> 5020 * Can be null if create is false and the metadata doesn't exist yet. 5021 * @throws RepositoryException if an error occurs. 5022 */ 5023 protected ModifiableCompositeMetadata _getRelationMetaHolder(ModifiableContent refContent, ContentType refContentType, String metadataPath, String searchContentId, boolean create) throws RepositoryException 5024 { 5025 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 5026 5027 if (pathSegments.length == 0) 5028 { 5029 return null; 5030 } 5031 5032 ModifiableCompositeMetadata metadataHolder = refContent.getMetadataHolder(); 5033 5034 try 5035 { 5036 MetadataDefinition metadataDef = null; 5037 5038 for (int i = 0; i < pathSegments.length - 1 && metadataHolder != null; i++) 5039 { 5040 if (i == 0) 5041 { 5042 metadataDef = refContentType.getMetadataDefinition(pathSegments[0]); 5043 } 5044 else if (metadataDef != null) 5045 { 5046 metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); 5047 } 5048 5049 if (metadataDef != null && metadataDef.getType() == MetadataType.COMPOSITE) 5050 { 5051 if (!create && !ExternalizableMetadataHelper.hasMetadata(metadataHolder, pathSegments[i], ExternalizableMetadataStatus.LOCAL)) 5052 { 5053 return null; 5054 } 5055 5056 metadataHolder = ExternalizableMetadataHelper.getCompositeMetadata(metadataHolder, pathSegments[i]); 5057 5058 if (metadataDef instanceof RepeaterDefinition) 5059 { 5060 String[] repeaterPathSegments = ArrayUtils.subarray(pathSegments, i + 1, pathSegments.length); 5061 RepeaterDefinition repeaterDef = (RepeaterDefinition) metadataDef; 5062 5063 // Search the repeater entry referencing the content being modified (by its ID). 5064 metadataHolder = _getEntryHolder(refContent, metadataHolder, repeaterDef, repeaterPathSegments, searchContentId, create); 5065 if (!create && metadataHolder == null) 5066 { 5067 return null; 5068 } 5069 } 5070 } 5071 } 5072 5073 return metadataHolder; 5074 } 5075 catch (UnknownMetadataException e) 5076 { 5077 // Ignore, just return null. 5078 return null; 5079 } 5080 } 5081 5082 /** 5083 * On a repeater metadata, search the entry referencing the content being modified. 5084 * @param refContent the content being referenced. 5085 * @param repeaterMeta the repeater metadata. 5086 * @param repeaterDef the repeater definition. 5087 * @param pathElements the path of the mutual relation from the repeater, as a String array. 5088 * @param searchContentId the ID of the content being modified. 5089 * @param create true to create non-existing composites and repeater entries. 5090 * @return The metadata holder of the relation, on the content being referenced.<br> 5091 * Can be null if create is false and the metadata doesn't exist yet. 5092 * @throws RepositoryException if an error occurs. 5093 */ 5094 private ModifiableCompositeMetadata _getEntryHolder(ModifiableContent refContent, ModifiableCompositeMetadata repeaterMeta, RepeaterDefinition repeaterDef, String[] pathElements, String searchContentId, boolean create) throws RepositoryException 5095 { 5096 int count = 0; 5097 int max = 0; 5098 for (String subMetaName : repeaterMeta.getMetadataNames()) 5099 { 5100 if (repeaterMeta.getType(subMetaName) == org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.COMPOSITE) 5101 { 5102 try 5103 { 5104 count++; 5105 max = Math.max(max, NumberUtils.toInt(subMetaName)); 5106 ModifiableCompositeMetadata entryHolder = ExternalizableMetadataHelper.getCompositeMetadata(repeaterMeta, subMetaName); 5107 ModifiableCompositeMetadata meta = entryHolder; 5108 5109 for (int i = 0; i < pathElements.length - 1 && meta != null; i++) 5110 { 5111 meta = ExternalizableMetadataHelper.getCompositeMetadata(meta, pathElements[i]); 5112 } 5113 5114 String[] ids = meta.getStringArray(pathElements[pathElements.length - 1], new String[0]); 5115 if (ArrayUtils.contains(ids, searchContentId)) 5116 { 5117 return entryHolder; 5118 } 5119 } 5120 catch (UnknownMetadataException e) 5121 { 5122 // Ignore. 5123 } 5124 } 5125 } 5126 5127 if (create) 5128 { 5129 int maxSize = repeaterDef.getMaxSize(); 5130 if (maxSize > 0 && count >= repeaterDef.getMaxSize()) 5131 { 5132 throw new RepositoryException("Unable to create repeater entry in content " + refContent + ", max size attained (" + repeaterDef.getMaxSize() + ")."); 5133 } 5134 5135 String newEntryName = Integer.toString(max + 1); 5136 return ExternalizableMetadataHelper.getCompositeMetadata(repeaterMeta, newEntryName); 5137 } 5138 5139 return null; 5140 } 5141 5142 /** 5143 * Get the composite metadata holding the metadata given by its path 5144 * @param parentMetadata The parent composite metadata 5145 * @param metadataPath The metadata path (with /) 5146 * @return The direct parent metadata 5147 */ 5148 protected ModifiableCompositeMetadata _getMetadataHolder(ModifiableCompositeMetadata parentMetadata, String metadataPath) 5149 { 5150 int pos = metadataPath.indexOf("/"); 5151 if (pos == -1) 5152 { 5153 return parentMetadata; 5154 } 5155 else 5156 { 5157 return _getMetadataHolder(parentMetadata.getCompositeMetadata(metadataPath.substring(0, pos), true), metadataPath.substring(pos + 1)); 5158 } 5159 } 5160 5161 /** 5162 * Trigger a 'edit content' workflow action (if the content is workflow-aware). 5163 * @param content The content. 5164 * @param actionId The current 'edit content' action ID. 5165 * @throws WorkflowException if an error occurs. 5166 */ 5167 protected void _triggerEditWorkflowAction(Content content, int actionId) throws WorkflowException 5168 { 5169 if (content instanceof WorkflowAwareContent) 5170 { 5171 Map<String, Object> inputs = new HashMap<>(); 5172 Map<String, Object> parameters = new HashMap<>(); 5173 5174 inputs.put(EditContentFunction.EDIT_MUTUAL_RELATIONSHIP, true); 5175 inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, parameters); 5176 5177 // Do action regarless of user's rights because user's rights was already checked during preparing process 5178 // This is necessary because the removal of a invert relation could removed the user rights in a referenced content, whereas user has authorized to edit the content before this removal 5179 inputs.put(CheckRightsCondition.FORCE, true); 5180 5181 parameters.put(FORM_RAW_VALUES, Collections.EMPTY_MAP); // No values 5182 parameters.put(QUIT, true); 5183 5184 _workflowHelper.doAction((WorkflowAwareContent) content, actionId, inputs); 5185 } 5186 } 5187 5188 private ExternalizableMetadataStatus _getExternalizableStatus (String rawValue) 5189 { 5190 Map<String, Object> externalizableValue = _jsonUtils.convertJsonToMap(rawValue); 5191 return ExternalizableMetadataStatus.valueOf(((String) externalizableValue.get("status")).toUpperCase()); 5192 } 5193 5194 private void _setMetadata(ModifiableCompositeMetadata metadata, String metadataName, Form form, Object value, boolean externalizable) 5195 { 5196 if (externalizable) 5197 { 5198 ExternalizableMetadataStatus status = form.getExternalizableField(metadataName).getStatus(); 5199 ExternalizableMetadataHelper.setLocalMetadata(metadata, metadataName, value, status); 5200 } 5201 else 5202 { 5203 ExternalizableMetadataHelper.setMetadata(metadata, metadataName, value); 5204 } 5205 } 5206}