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