예제 #1
0
    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);
        }
예제 #3
0
        /// <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 : "";
            }
        }
예제 #5
0
        /// <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);
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        /// <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;
        }