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