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