private static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int?contentId, ref string?culture, ref string?segment) { if (culture != null && segment != null) { return; } // use context values VariationContext?publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) { culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : string.Empty; } if (segment == null) { if (variations.VariesBySegment()) { segment = contentId == null ? publishedVariationContext?.Segment : publishedVariationContext?.GetSegment(contentId.Value); } else { segment = string.Empty; } } }
/// <summary> /// Validates that a combination of culture and segment is valid for the variation. /// </summary> /// <param name="variation">The variation.</param> /// <param name="culture">The culture.</param> /// <param name="segment">The segment.</param> /// <param name="exact">A value indicating whether to perform exact validation.</param> /// <param name="wildcards">A value indicating whether to support wildcards.</param> /// <param name="throwIfInvalid">A value indicating whether to throw a <see cref="NotSupportedException"/> when the combination is invalid.</param> /// <returns>True if the combination is valid; otherwise false.</returns> /// <remarks> /// <para>When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then /// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is /// Culture, an invariant combination is ok.</para> /// <para>Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type.</para> /// <para>Both <paramref name="culture"/> and <paramref name="segment"/> can be "*" to indicate "all of them".</para> /// </remarks> /// <exception cref="NotSupportedException">Occurs when the combination is invalid, and <paramref name="throwIfInvalid"/> is true.</exception> public static bool ValidateVariation(this ContentVariation variation, string culture, string segment, bool exact, bool wildcards, bool throwIfInvalid) { culture = culture.NullOrWhiteSpaceAsNull(); segment = segment.NullOrWhiteSpaceAsNull(); // if wildcards are disabled, do not allow "*" if (!wildcards && (culture == "*" || segment == "*")) { if (throwIfInvalid) { throw new NotSupportedException($"Variation wildcards are not supported."); } return(false); } if (variation.VariesByCulture()) { // varies by culture // in exact mode, the culture cannot be null if (exact && culture == null) { if (throwIfInvalid) { throw new NotSupportedException($"Culture may not be null because culture variation is enabled."); } return(false); } } else { // does not vary by culture // the culture cannot have a value // unless wildcards and it's "*" if (culture != null && !(wildcards && culture == "*")) { if (throwIfInvalid) { throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled."); } return(false); } } // if it does not vary by segment // the segment cannot have a value // segment may always be null, even when the ContentVariation.Segment flag is set for this variation, // therefore the exact parameter is not used in segment validation. if (!variation.VariesBySegment() && segment != null && !(wildcards && segment == "*")) { if (throwIfInvalid) { throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled."); } return(false); } return(true); }
/// <summary> /// Validates that a combination of culture and segment is valid for the variation. /// </summary> /// <param name="variation">The variation.</param> /// <param name="culture">The culture.</param> /// <param name="segment">The segment.</param> /// <param name="exact">A value indicating whether to perform exact validation.</param> /// <param name="wildcards">A value indicating whether to support wildcards.</param> /// <param name="throwIfInvalid">A value indicating whether to throw a <see cref="NotSupportedException"/> when the combination is invalid.</param> /// <returns>True if the combination is valid; otherwise false.</returns> /// <remarks> /// <para>When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then /// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is /// Culture, an invariant combination is ok.</para> /// <para>Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type.</para> /// <para>Both <paramref name="culture"/> and <paramref name="segment"/> can be "*" to indicate "all of them".</para> /// </remarks> /// <exception cref="NotSupportedException">Occurs when the combination is invalid, and <paramref name="throwIfInvalid"/> is true.</exception> public static bool ValidateVariation(this ContentVariation variation, string culture, string segment, bool exact, bool wildcards, bool throwIfInvalid) { culture = culture.NullOrWhiteSpaceAsNull(); segment = segment.NullOrWhiteSpaceAsNull(); bool Validate(bool variesBy, string value) { if (variesBy) { // varies by // in exact mode, the value cannot be null (but it can be a wildcard) // in !wildcards mode, the value cannot be a wildcard (but it can be null) if ((exact && value == null) || (!wildcards && value == "*")) { return(false); } } else { // does not vary by value // the value cannot have a value // unless wildcards and it's "*" if (value != null && (!wildcards || value != "*")) { return(false); } } return(true); } if (!Validate(variation.VariesByCulture(), culture)) { if (throwIfInvalid) { throw new NotSupportedException($"Culture value \"{culture ?? "<null>"}\" is invalid."); } return(false); } if (!Validate(variation.VariesBySegment(), segment)) { if (throwIfInvalid) { throw new NotSupportedException($"Segment value \"{segment ?? "<null>"}\" is invalid."); } return(false); } return(true); }
public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment) { if (culture != null && segment != null) { return; } // use context values var publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) { culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; } if (segment == null) { segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; } }
/// <summary> /// Creates a collection of <see cref="PropertyDataDto"/> from a collection of <see cref="Property"/> /// </summary> /// <param name="contentVariation"> /// The <see cref="ContentVariation"/> of the entity containing the collection of <see cref="Property"/> /// </param> /// <param name="currentVersionId"></param> /// <param name="publishedVersionId"></param> /// <param name="properties">The properties to map</param> /// <param name="languageRepository"></param> /// <param name="edited">out parameter indicating that one or more properties have been edited</param> /// <param name="editedCultures"> /// Out parameter containing a collection of edited cultures when the contentVariation varies by culture. /// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table. /// </param> /// <returns></returns> public static IEnumerable <PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable <IProperty> properties, ILanguageRepository languageRepository, out bool edited, out HashSet <string>?editedCultures) { var propertyDataDtos = new List <PropertyDataDto>(); edited = false; editedCultures = null; // don't allocate unless necessary string?defaultCulture = null; //don't allocate unless necessary var entityVariesByCulture = contentVariation.VariesByCulture(); // create dtos for each property values, but only for values that do actually exist // ie have a non-null value, everything else is just ignored and won't have a db row foreach (var property in properties) { if (property.PropertyType.SupportsPublishing) { //create the resulting hashset if it's not created and the entity varies by culture if (entityVariesByCulture && editedCultures == null) { editedCultures = new HashSet <string>(StringComparer.OrdinalIgnoreCase); } // publishing = deal with edit and published values foreach (var propertyValue in property.Values) { var isInvariantValue = propertyValue.Culture == null && propertyValue.Segment == null; var isCultureValue = propertyValue.Culture != null; var isSegmentValue = propertyValue.Segment != null; // deal with published value if ((propertyValue.PublishedValue != null || isSegmentValue) && publishedVersionId > 0) { propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue?.Segment, propertyValue?.PublishedValue)); } // deal with edit value if (propertyValue?.EditedValue != null || isSegmentValue) { propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue?.Culture), propertyValue?.Segment, propertyValue?.EditedValue)); } // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the // administrator has previously changed the property type to be variant vs invariant. // We need to check for this scenario here because otherwise the editedCultures and edited flags // will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to // only process edited cultures based on the current value type and how the property varies. // The above logic will still persist the currently saved property value for each culture in case the admin // decides to swap the property's variance again, in which case the edited flag will be recalculated. if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue) { continue; } // use explicit equals here, else object comparison fails at comparing eg strings var sameValues = propertyValue?.PublishedValue == null ? propertyValue?.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); edited |= !sameValues; if (entityVariesByCulture && !sameValues) { if (isCultureValue && propertyValue?.Culture is not null) { editedCultures?.Add(propertyValue.Culture); // report culture as edited } else if (isInvariantValue) { // flag culture as edited if it contains an edited invariant property if (defaultCulture == null) { defaultCulture = languageRepository.GetDefaultIsoCode(); } editedCultures?.Add(defaultCulture); } } } } else { foreach (var propertyValue in property.Values) { // not publishing = only deal with edit values if (propertyValue.EditedValue != null) { propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); } } edited = true; } } return(propertyDataDtos); }
/// <summary> /// Creates a collection of <see cref="PropertyDataDto"/> from a collection of <see cref="Property"/> /// </summary> /// <param name="contentVariation"> /// The <see cref="ContentVariation"/> of the entity containing the collection of <see cref="Property"/> /// </param> /// <param name="currentVersionId"></param> /// <param name="publishedVersionId"></param> /// <param name="properties">The properties to map</param> /// <param name="languageRepository"></param> /// <param name="edited">out parameter indicating that one or more properties have been edited</param> /// <param name="editedCultures">out parameter containing a collection of of edited cultures when the contentVariation varies by culture</param> /// <returns></returns> public static IEnumerable <PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable <Property> properties, ILanguageRepository languageRepository, out bool edited, out HashSet <string> editedCultures) { var propertyDataDtos = new List <PropertyDataDto>(); edited = false; editedCultures = null; // don't allocate unless necessary string defaultCulture = null; //don't allocate unless necessary var entityVariesByCulture = contentVariation.VariesByCulture(); foreach (var property in properties) { if (property.PropertyType.IsPublishing) { //create the resulting hashset if it's not created and the entity varies by culture if (entityVariesByCulture && editedCultures == null) { editedCultures = new HashSet <string>(StringComparer.OrdinalIgnoreCase); } // publishing = deal with edit and published values foreach (var propertyValue in property.Values) { // deal with published value if (propertyValue.PublishedValue != null && publishedVersionId > 0) { propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); } // deal with edit value if (propertyValue.EditedValue != null) { propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); } // deal with missing edit value (fix inconsistencies) else if (propertyValue.PublishedValue != null) { propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); } // use explicit equals here, else object comparison fails at comparing eg strings var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); edited |= !sameValues; if (entityVariesByCulture && // cultures can be edited, ie CultureNeutral is supported propertyValue.Culture != null && propertyValue.Segment == null && // and value is CultureNeutral !sameValues) // and edited and published are different { editedCultures.Add(propertyValue.Culture); // report culture as edited } // flag culture as edited if it contains an edited invariant property if (propertyValue.Culture == null && //invariant property !sameValues && // and edited and published are different entityVariesByCulture) //only when the entity is variant { if (defaultCulture == null) { defaultCulture = languageRepository.GetDefaultIsoCode(); } editedCultures.Add(defaultCulture); } } } else { foreach (var propertyValue in property.Values) { // not publishing = only deal with edit values if (propertyValue.EditedValue != null) { propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); } } edited = true; } } return(propertyDataDtos); }
/// <summary> /// Tries to create an impact instance representing the impact of a culture set, /// in the context of a content item variation. /// </summary> /// <param name="culture">The culture code.</param> /// <param name="isDefault">A value indicating whether the culture is the default culture.</param> /// <param name="variation">A content variation.</param> /// <param name="throwOnFail">A value indicating whether to throw if the impact cannot be created.</param> /// <param name="impact">The impact if it could be created, otherwise null.</param> /// <returns>A value indicating whether the impact could be created.</returns> /// <remarks> /// <para>Validates that the culture is compatible with the variation.</para> /// </remarks> internal static bool TryCreate(string culture, bool isDefault, ContentVariation variation, bool throwOnFail, out CultureImpact impact) { impact = null; // if culture is invariant... if (culture == null) { // ... then variation must not vary by culture ... if (variation.VariesByCulture()) { if (throwOnFail) throw new InvalidOperationException("The invariant culture is not compatible with a varying variation."); return false; } // ... and it cannot be default if (isDefault) { if (throwOnFail) throw new InvalidOperationException("The invariant culture can not be the default culture."); return false; } impact = Invariant; return true; } // if culture is 'all'... if (culture == "*") { // ... it cannot be default if (isDefault) { if (throwOnFail) throw new InvalidOperationException("The 'all' culture can not be the default culture."); return false; } // if variation does not vary by culture, then impact is invariant impact = variation.VariesByCulture() ? All : Invariant; return true; } // neither null nor "*" - cannot be the empty string if (culture.IsNullOrWhiteSpace()) { if (throwOnFail) throw new ArgumentException("Cannot be the empty string.", nameof(culture)); return false; } // if culture is specific, then variation must vary if (!variation.VariesByCulture()) { if (throwOnFail) throw new InvalidOperationException($"The variant culture {culture} is not compatible with an invariant variation."); return false; } // return specific impact impact = new CultureImpact(culture, isDefault); return true; }