Merge branch 'temp8-I3089' of https://github.com/umbraco/Umbraco-CMS into temp8-I3089
This commit is contained in:
@@ -159,6 +159,9 @@ namespace Umbraco.Core.Composing
|
||||
public static IPublishedValueFallback PublishedValueFallback
|
||||
=> _publishedValueFallback ?? Container.GetInstance<IPublishedValueFallback>() ?? new NoopPublishedValueFallback();
|
||||
|
||||
public static IVariationContextAccessor VariationContextAccessor
|
||||
=> Container.GetInstance<IVariationContextAccessor>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
[ConfigurationProperty("loginBackgroundImage")]
|
||||
internal InnerTextConfigurationElement<string> LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty);
|
||||
[ConfigurationProperty("StripUdiAttributes")]
|
||||
internal InnerTextConfigurationElement<bool> StripUdiAttributes
|
||||
{
|
||||
get { return GetOptionalTextElement("StripUdiAttributes", true); }
|
||||
}
|
||||
|
||||
|
||||
string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress;
|
||||
|
||||
@@ -136,6 +142,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes;
|
||||
|
||||
bool IContentSection.StripUdiAttributes => StripUdiAttributes;
|
||||
|
||||
string IContentSection.LoginBackgroundImage => LoginBackgroundImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
IEnumerable<string> ImageTagAllowedAttributes { get; }
|
||||
|
||||
IEnumerable<IImagingAutoFillUploadField> ImageAutoFillProperties { get; }
|
||||
|
||||
|
||||
string ScriptFolderPath { get; }
|
||||
|
||||
IEnumerable<string> ScriptFileTypes { get; }
|
||||
@@ -66,5 +66,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
bool EnableInheritedMediaTypes { get; }
|
||||
|
||||
string LoginBackgroundImage { get; }
|
||||
bool StripUdiAttributes { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,11 @@ namespace Umbraco.Core
|
||||
/// Property alias for the Media's file extension.
|
||||
/// </summary>
|
||||
public const string Extension = "umbracoExtension";
|
||||
|
||||
/// <summary>
|
||||
/// The default height/width of an image file if the size can't be determined from the metadata
|
||||
/// </summary>
|
||||
public const int DefaultSize = 200;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
/// </remarks>
|
||||
public const string RecycleBinMediaPathPrefix = "-1,-21,";
|
||||
|
||||
public const int DefaultLabelDataTypeId = -92;
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoUpgradePlanName = "Umbraco.Core";
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ namespace Umbraco.Core.Events
|
||||
|
||||
// fixme see notes above
|
||||
// delete event args does NOT superceedes 'unpublished' event
|
||||
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished")
|
||||
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "Unpublished")
|
||||
return false;
|
||||
|
||||
// found occurences, need to determine if this event args is superceded
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
private void CreateLanguageData()
|
||||
{
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefaultVariantLanguage = true });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true });
|
||||
}
|
||||
|
||||
private void CreateContentChildTypeData()
|
||||
|
||||
@@ -107,18 +107,35 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
|
||||
Chain<UserForeignKeys>("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}"); // shannon added that one - let's keep it as the default path
|
||||
//Chain<AddTypedLabels>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one = merge conflict, remove,
|
||||
Chain<AddTypedLabels>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but it after shannon's, with a new target state,
|
||||
Chain<AddTypedLabels>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but add it after shannon's, with a new target state,
|
||||
Add<UserForeignKeys>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}", "{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // and provide a path out of the conflict state
|
||||
// resume at {4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4} ...
|
||||
|
||||
Chain<ContentVariationMigration>("{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
Chain<UpdateUmbracoConsent>("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0
|
||||
//Chain<FallbackLanguage>("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); // andy added that one = merge conflict, remove
|
||||
Chain<AddRelationTypeForMediaFolderOnDelete>("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0
|
||||
Chain<IncreaseLanguageIsoCodeColumnLength>("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0
|
||||
Chain<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
|
||||
Chain<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
|
||||
Chain<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}");
|
||||
Chain<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
//Chain<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove
|
||||
|
||||
Chain<FallbackLanguage>("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state
|
||||
From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // and provide a path out of andy's
|
||||
.CopyChain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // to next
|
||||
// resume at {8B14CEBD-EE47-4AAD-A841-93551D917F11} ...
|
||||
|
||||
Chain<UpdateDefaultMandatoryLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // add stephan's after others, with a new target state
|
||||
From("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}") // and provide a path out of stephan's
|
||||
.Chain<FallbackLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // to next
|
||||
// resume at {5F4597F4-A4E0-4AFE-90B5-6D2F896830EB} ...
|
||||
|
||||
//Chain<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
Chain<RefactorVariantsModel>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
From("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}")
|
||||
.Chain<FallbackLanguage>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
// resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}...
|
||||
|
||||
//FINAL
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new, self-joined field to umbracoLanguages to hold the fall-back language for
|
||||
/// a given language.
|
||||
/// </summary>
|
||||
public class FallbackLanguage : MigrationBase
|
||||
{
|
||||
public FallbackLanguage(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.Language) && x.ColumnName.InvariantEquals("fallbackLanguageId")) == false)
|
||||
AddColumn<LanguageDto>("fallbackLanguageId");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var defaultId = int.MaxValue;
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
if (dto.IsDefaultVariantLanguage)
|
||||
if (dto.IsDefault)
|
||||
{
|
||||
defaultId = dto.Id;
|
||||
break;
|
||||
@@ -38,8 +38,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
// update, so that language with that id is now default and mandatory
|
||||
var updateDefault = Sql()
|
||||
.Update<LanguageDto>(u => u
|
||||
.Set(x => x.IsDefaultVariantLanguage, true)
|
||||
.Set(x => x.Mandatory, true))
|
||||
.Set(x => x.IsDefault, true)
|
||||
.Set(x => x.IsMandatory, true))
|
||||
.Where<LanguageDto>(x => x.Id == defaultId);
|
||||
|
||||
Database.Execute(updateDefault);
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
/// <summary>
|
||||
/// Used when nodes are unpublished
|
||||
/// </summary>
|
||||
UnPublish,
|
||||
Unpublish,
|
||||
/// <summary>
|
||||
/// Used when nodes are moved
|
||||
/// </summary>
|
||||
|
||||
@@ -4,34 +4,54 @@ using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a language.
|
||||
/// </summary>
|
||||
public interface ILanguage : IEntity, IRememberBeingDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Iso Code for the Language
|
||||
/// Gets or sets the ISO code of the language.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
string IsoCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Culture Name for the Language
|
||||
/// Gets or sets the culture name of the language.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
string CultureName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CultureInfo"/> object for the current Language
|
||||
/// Gets the <see cref="CultureInfo"/> object for the language.
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
CultureInfo CultureInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines if this language is the default variant language when language variants are in use
|
||||
/// Gets or sets a value indicating whether the language is the default language.
|
||||
/// </summary>
|
||||
bool IsDefaultVariantLanguage { get; set; }
|
||||
[DataMember]
|
||||
bool IsDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, a variant node cannot be published unless this language variant is created
|
||||
/// Gets or sets a value indicating whether the language is mandatory.
|
||||
/// </summary>
|
||||
bool Mandatory { get; set; }
|
||||
/// <remarks>
|
||||
/// <para>When a language is mandatory, a multi-lingual document cannot be published
|
||||
/// without that language being published, and unpublishing that language unpublishes
|
||||
/// the entire document.</para>
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
bool IsMandatory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of a fallback language.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The fallback language can be used in multi-lingual scenarios, to help
|
||||
/// define fallback strategies when a value does not exist for a requested language.</para>
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Core.Models
|
||||
private string _cultureName;
|
||||
private bool _isDefaultVariantLanguage;
|
||||
private bool _mandatory;
|
||||
private int? _fallbackLanguageId;
|
||||
|
||||
public Language(string isoCode)
|
||||
{
|
||||
@@ -30,13 +31,12 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.IsoCode);
|
||||
public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.CultureName);
|
||||
public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsDefaultVariantLanguage);
|
||||
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.Mandatory);
|
||||
public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsDefault);
|
||||
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsMandatory);
|
||||
public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, int?>(x => x.FallbackLanguageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Iso Code for the Language
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string IsoCode
|
||||
{
|
||||
@@ -44,9 +44,7 @@ namespace Umbraco.Core.Models
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Culture Name for the Language
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string CultureName
|
||||
{
|
||||
@@ -54,22 +52,29 @@ namespace Umbraco.Core.Models
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CultureInfo"/> object for the current Language
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode);
|
||||
|
||||
public bool IsDefaultVariantLanguage
|
||||
/// <inheritdoc />
|
||||
public bool IsDefault
|
||||
{
|
||||
get => _isDefaultVariantLanguage;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector);
|
||||
}
|
||||
|
||||
public bool Mandatory
|
||||
/// <inheritdoc />
|
||||
public bool IsMandatory
|
||||
{
|
||||
get => _mandatory;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? FallbackLanguageId
|
||||
{
|
||||
get => _fallbackLanguageId;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of tag changes.
|
||||
/// </summary>
|
||||
internal class PropertyTagChange
|
||||
{
|
||||
public ChangeType Type { get; set; }
|
||||
|
||||
public IEnumerable<(string Type, string Tags)> Tags { get; set; }
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
Replace,
|
||||
Remove,
|
||||
Merge
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/Umbraco.Core/Models/PublishedContent/Fallback.cs
Normal file
75
src/Umbraco.Core/Models/PublishedContent/Fallback.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the built-in fallback policies.
|
||||
/// </summary>
|
||||
public struct Fallback : IEnumerable<int>
|
||||
{
|
||||
private readonly int[] _values;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Fallback"/> struct with values.
|
||||
/// </summary>
|
||||
private Fallback(int[] values)
|
||||
{
|
||||
_values = values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ordered set of fallback policies.
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
public static Fallback To(params int[] values) => new Fallback(values);
|
||||
|
||||
/// <summary>
|
||||
/// Do not fallback.
|
||||
/// </summary>
|
||||
public const int None = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Fallback to default value.
|
||||
/// </summary>
|
||||
public const int DefaultValue = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fallback to default value policy.
|
||||
/// </summary>
|
||||
public static Fallback ToDefaultValue => new Fallback(new[] { DefaultValue });
|
||||
|
||||
/// <summary>
|
||||
/// Fallback to other languages.
|
||||
/// </summary>
|
||||
public const int Language = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fallback to language policy.
|
||||
/// </summary>
|
||||
public static Fallback ToLanguage => new Fallback(new[] { Language });
|
||||
|
||||
/// <summary>
|
||||
/// Fallback to tree ancestors.
|
||||
/// </summary>
|
||||
public const int Ancestors = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fallback to tree ancestors policy.
|
||||
/// </summary>
|
||||
public static Fallback ToAncestors => new Fallback(new[] { Ancestors });
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<int> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<int>)_values ?? Array.Empty<int>()).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,122 @@
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a fallback strategy for getting <see cref="IPublishedElement"/> values.
|
||||
/// </summary>
|
||||
// fixme - IPublishedValueFallback is still WorkInProgress
|
||||
// todo - properly document methods, etc
|
||||
// todo - understand caching vs fallback (recurse etc)
|
||||
public interface IPublishedValueFallback
|
||||
{
|
||||
// note that at property level, property.GetValue() does NOT implement fallback, and one has
|
||||
// to get property.Value() or property.Value<T>() to trigger fallback
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a property.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever property.Value(culture, segment, defaultValue) is called, and
|
||||
/// property.HasValue(culture, segment) is false.</para>
|
||||
/// <para>It can only fallback at property level (no recurse).</para>
|
||||
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
|
||||
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
|
||||
/// <para>Note that <paramref name="culture"/> and <paramref name="segment"/> may not be contextualized,
|
||||
/// so the variant context should be used to contextualize them (see our default implementation in
|
||||
/// the web project.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value);
|
||||
|
||||
// this method is called whenever property.Value(culture, segment, defaultValue) is called, and
|
||||
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and
|
||||
/// property.HasValue(culture, segment) is false.</para>
|
||||
/// <para>It can only fallback at property level (no recurse).</para>
|
||||
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
|
||||
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value);
|
||||
|
||||
object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue);
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published element property.
|
||||
/// </summary>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// <para>It can only fallback at element level (no recurse).</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
|
||||
|
||||
// this method is called whenever property.Value<T>(culture, segment, defaultValue) is called, and
|
||||
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published element property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// <para>It can only fallback at element level (no recurse).</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
|
||||
|
||||
T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue);
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published content property.
|
||||
/// </summary>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
|
||||
|
||||
// these methods to be called whenever getting the property value for the specified alias, culture and segment,
|
||||
// either returned no property at all, or a property that does not HasValue for the specified culture and segment.
|
||||
|
||||
object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue);
|
||||
|
||||
T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue);
|
||||
|
||||
object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse);
|
||||
|
||||
T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse);
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published content property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,17 +49,24 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
/// <param name="modelTypes">The model types map.</param>
|
||||
/// <returns>The actual Clr type.</returns>
|
||||
public static Type Map(Type type, Dictionary<string, Type> modelTypes)
|
||||
=> Map(type, modelTypes, false);
|
||||
|
||||
public static Type Map(Type type, Dictionary<string, Type> modelTypes, bool dictionaryIsInvariant)
|
||||
{
|
||||
// it may be that senders forgot to send an invariant dictionary (garbage-in)
|
||||
if (!dictionaryIsInvariant)
|
||||
modelTypes = new Dictionary<string, Type>(modelTypes, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
if (type is ModelType modelType)
|
||||
{
|
||||
if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out Type actualType))
|
||||
if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out var actualType))
|
||||
return actualType;
|
||||
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\".");
|
||||
}
|
||||
|
||||
if (type is ModelTypeArrayType arrayType)
|
||||
{
|
||||
if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out Type actualType))
|
||||
if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out var actualType))
|
||||
return actualType.MakeArrayType();
|
||||
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\".");
|
||||
}
|
||||
@@ -70,7 +77,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes)).ToArray();
|
||||
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
|
||||
return def.MakeGenericType(args);
|
||||
}
|
||||
|
||||
@@ -81,7 +88,14 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
/// <param name="map">The model types map.</param>
|
||||
/// <returns>The actual Clr type name.</returns>
|
||||
public static string MapToName(Type type, Dictionary<string, string> map)
|
||||
=> MapToName(type, map, false);
|
||||
|
||||
private static string MapToName(Type type, Dictionary<string, string> map, bool dictionaryIsInvariant)
|
||||
{
|
||||
// it may be that senders forgot to send an invariant dictionary (garbage-in)
|
||||
if (!dictionaryIsInvariant)
|
||||
map = new Dictionary<string, string>(map, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
if (type is ModelType modelType)
|
||||
{
|
||||
if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName))
|
||||
@@ -102,7 +116,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => MapToName(x, map)).ToArray();
|
||||
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
|
||||
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
|
||||
return defFullName + "<" + string.Join(", ", args) + ">";
|
||||
}
|
||||
|
||||
@@ -9,21 +9,45 @@
|
||||
public class NoopPublishedValueFallback : IPublishedValueFallback
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue) => defaultValue;
|
||||
public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue) => defaultValue;
|
||||
public bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue) => defaultValue;
|
||||
public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue) => defaultValue;
|
||||
public bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) => defaultValue;
|
||||
public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) => defaultValue;
|
||||
public bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
public static class VariationContextAccessorExtensions
|
||||
{
|
||||
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 : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,33 +10,51 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
{
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.Language;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the language.
|
||||
/// </summary>
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(IdentitySeed = 2)]
|
||||
public short Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ISO code of the language.
|
||||
/// </summary>
|
||||
[Column("languageISOCode")]
|
||||
[Index(IndexTypes.UniqueNonClustered)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(14)]
|
||||
public string IsoCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the culture name of the language.
|
||||
/// </summary>
|
||||
[Column("languageCultureName")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(100)]
|
||||
public string CultureName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines if this language is the default variant language when language variants are in use
|
||||
/// Gets or sets a value indicating whether the language is the default language.
|
||||
/// </summary>
|
||||
[Column("isDefaultVariantLang")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool IsDefaultVariantLanguage { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, a variant node cannot be published unless this language variant is created
|
||||
/// Gets or sets a value indicating whether the language is mandatory.
|
||||
/// </summary>
|
||||
[Column("mandatory")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool Mandatory { get; set; }
|
||||
public bool IsMandatory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of a fallback language.
|
||||
/// </summary>
|
||||
[Column("fallbackLanguageId")]
|
||||
[ForeignKey(typeof(LanguageDto), Column = "id")]
|
||||
[Index(IndexTypes.NonClustered)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
CultureName = dto.CultureName,
|
||||
Id = dto.Id,
|
||||
IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage,
|
||||
Mandatory = dto.Mandatory
|
||||
IsDefault = dto.IsDefault,
|
||||
IsMandatory = dto.IsMandatory,
|
||||
FallbackLanguageId = dto.FallbackLanguageId
|
||||
};
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
@@ -27,12 +28,15 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
CultureName = entity.CultureName,
|
||||
IsoCode = entity.IsoCode,
|
||||
IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage,
|
||||
Mandatory = entity.Mandatory
|
||||
IsDefault = entity.IsDefault,
|
||||
IsMandatory = entity.IsMandatory,
|
||||
FallbackLanguageId = entity.FallbackLanguageId
|
||||
};
|
||||
|
||||
if (entity.HasIdentity)
|
||||
{
|
||||
dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var sqlClause = GetBaseQuery(false);
|
||||
var translator = new SqlTranslator<ILanguage>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
return Database.Fetch<LanguageDto>(sql).Select(ConvertFromDto);
|
||||
var dtos = Database.Fetch<LanguageDto>(sql);
|
||||
return dtos.Select(ConvertFromDto).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -115,10 +116,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return list;
|
||||
}
|
||||
|
||||
protected override Guid NodeObjectTypeId
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
protected override Guid NodeObjectTypeId => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -133,15 +131,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
((EntityBase) entity).AddingEntity();
|
||||
|
||||
// deal with entity becoming the new default entity
|
||||
if (entity.IsDefaultVariantLanguage)
|
||||
if (entity.IsDefault)
|
||||
{
|
||||
// set all other entities to non-default
|
||||
// safe (no race cond) because the service locks languages
|
||||
var setAllDefaultToFalse = Sql()
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefaultVariantLanguage, false));
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefault, false));
|
||||
Database.Execute(setAllDefaultToFalse);
|
||||
}
|
||||
;
|
||||
|
||||
// fallback cycles are detected at service level
|
||||
|
||||
// insert
|
||||
var dto = LanguageFactory.BuildDto(entity);
|
||||
var id = Convert.ToInt32(Database.Insert(dto));
|
||||
@@ -157,14 +157,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
((EntityBase) entity).UpdatingEntity();
|
||||
|
||||
if (entity.IsDefaultVariantLanguage)
|
||||
if (entity.IsDefault)
|
||||
{
|
||||
// deal with entity becoming the new default entity
|
||||
|
||||
// set all other entities to non-default
|
||||
// safe (no race cond) because the service locks languages
|
||||
var setAllDefaultToFalse = Sql()
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefaultVariantLanguage, false));
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefault, false));
|
||||
Database.Execute(setAllDefaultToFalse);
|
||||
}
|
||||
else
|
||||
@@ -174,13 +174,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var selectDefaultId = Sql()
|
||||
.Select<LanguageDto>(x => x.Id)
|
||||
.From<LanguageDto>()
|
||||
.Where<LanguageDto>(x => x.IsDefaultVariantLanguage);
|
||||
.Where<LanguageDto>(x => x.IsDefault);
|
||||
|
||||
var defaultId = Database.ExecuteScalar<int>(selectDefaultId);
|
||||
if (entity.Id == defaultId)
|
||||
throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
|
||||
}
|
||||
|
||||
// fallback cycles are detected at service level
|
||||
|
||||
// update
|
||||
var dto = LanguageFactory.BuildDto(entity);
|
||||
Database.Update(dto);
|
||||
@@ -195,12 +197,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var selectDefaultId = Sql()
|
||||
.Select<LanguageDto>(x => x.Id)
|
||||
.From<LanguageDto>()
|
||||
.Where<LanguageDto>(x => x.IsDefaultVariantLanguage);
|
||||
.Where<LanguageDto>(x => x.IsDefault);
|
||||
|
||||
var defaultId = Database.ExecuteScalar<int>(selectDefaultId);
|
||||
if (entity.Id == defaultId)
|
||||
throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode}).");
|
||||
|
||||
// We need to remove any references to the language if it's being used as a fall-back from other ones
|
||||
var clearFallbackLanguage = Sql()
|
||||
.Update<LanguageDto>(u => u
|
||||
.Set(x => x.FallbackLanguageId, null))
|
||||
.Where<LanguageDto>(x => x.FallbackLanguageId == entity.Id);
|
||||
|
||||
Database.Execute(clearFallbackLanguage);
|
||||
|
||||
// delete
|
||||
base.PersistDeletedItem(entity);
|
||||
}
|
||||
@@ -212,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var entity = LanguageFactory.BuildEntity(dto);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
public ILanguage GetByIsoCode(string isoCode)
|
||||
{
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
@@ -271,7 +281,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// get all cached, non-cloned
|
||||
var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList();
|
||||
var language = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage);
|
||||
var language = languages.FirstOrDefault(x => x.IsDefault);
|
||||
if (language != null) return language;
|
||||
|
||||
// this is an anomaly, the service/repo should ensure it cannot happen
|
||||
|
||||
@@ -748,7 +748,7 @@ ORDER BY colName";
|
||||
|
||||
if (excludeUserGroups != null && excludeUserGroups.Length > 0)
|
||||
{
|
||||
var subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id
|
||||
const string subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id
|
||||
FROM umbracoUser
|
||||
INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id
|
||||
INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId
|
||||
@@ -809,7 +809,7 @@ ORDER BY colName";
|
||||
sql = new SqlTranslator<IUser>(sql, query).Translate();
|
||||
|
||||
// get sorted and filtered sql
|
||||
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql), orderDirection, orderBy);
|
||||
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderDirection, orderBy);
|
||||
|
||||
// get a page of results and total count
|
||||
var pagedResult = Database.Page<UserDto>(pageIndex + 1, pageSize, sqlNodeIdsWithSort);
|
||||
@@ -820,11 +820,17 @@ ORDER BY colName";
|
||||
return pagedResult.Items.Select(UserFactory.BuildEntity);
|
||||
}
|
||||
|
||||
private Sql<ISqlContext> ApplyFilter(Sql<ISqlContext> sql, Sql<ISqlContext> filterSql)
|
||||
private Sql<ISqlContext> ApplyFilter(Sql<ISqlContext> sql, Sql<ISqlContext> filterSql, bool hasWhereClause)
|
||||
{
|
||||
if (filterSql == null) return sql;
|
||||
|
||||
sql.Append(SqlContext.Sql(" WHERE " + filterSql.SQL.TrimStart("AND "), filterSql.Arguments));
|
||||
//ensure we don't append a WHERE if there is already one
|
||||
var args = filterSql.Arguments;
|
||||
var sqlFilter = hasWhereClause
|
||||
? filterSql.SQL
|
||||
: " WHERE " + filterSql.SQL.TrimStart("AND ");
|
||||
|
||||
sql.Append(SqlContext.Sql(sqlFilter, args));
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
@@ -63,5 +65,6 @@ namespace Umbraco.Core.Security
|
||||
/// Used so that we aren't creating a new CultureInfo object for every single request
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,7 +635,9 @@ namespace Umbraco.Core.Security
|
||||
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
//if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
|
||||
var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
user.LastLoginDate = dt;
|
||||
}
|
||||
if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
|
||||
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|
||||
|
||||
@@ -988,14 +988,14 @@ namespace Umbraco.Core.Services.Implement
|
||||
UnpublishResultType result;
|
||||
if (culture == "*" || culture == null)
|
||||
{
|
||||
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id);
|
||||
result = UnpublishResultType.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
Audit(AuditType.UnPublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id);
|
||||
if (!content.Published)
|
||||
Audit(AuditType.UnPublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id);
|
||||
result = content.Published ? UnpublishResultType.SuccessCulture : UnpublishResultType.SuccessMandatoryCulture;
|
||||
}
|
||||
scope.Complete();
|
||||
@@ -1034,7 +1034,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
var cannotBePublished = publishedCultures.Count == 0; // no published cultures = cannot be published
|
||||
if (!cannotBePublished)
|
||||
{
|
||||
var mandatoryCultures = _languageRepository.GetMany().Where(x => x.Mandatory).Select(x => x.IsoCode);
|
||||
var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
|
||||
cannotBePublished = mandatoryCultures.Any(x => !publishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); // missing mandatory culture = cannot be published
|
||||
}
|
||||
|
||||
@@ -1120,9 +1120,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (unpublishResult.Success) // and succeeded, trigger events
|
||||
{
|
||||
// events and audit
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), "UnPublished");
|
||||
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), "Unpublished");
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
|
||||
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id);
|
||||
scope.Complete();
|
||||
return new PublishResult(PublishResultType.Success, evtMsgs, content);
|
||||
}
|
||||
@@ -1348,10 +1348,10 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
// if it's not trashed yet, and published, we should unpublish
|
||||
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// just raise the event
|
||||
if (content.Trashed == false && content.Published)
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(UnPublished));
|
||||
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(Unpublished));
|
||||
|
||||
DeleteLocked(scope, content);
|
||||
|
||||
@@ -2098,12 +2098,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <summary>
|
||||
/// Occurs before unpublish
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> UnPublishing;
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Unpublishing;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after unpublish
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> UnPublished;
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Unpublished;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after change.
|
||||
@@ -2197,8 +2197,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
// ensures that a document can be unpublished
|
||||
internal UnpublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs)
|
||||
{
|
||||
// raise UnPublishing event
|
||||
if (scope.Events.DispatchCancelable(UnPublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
|
||||
// raise Unpublishing event
|
||||
if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
|
||||
{
|
||||
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id);
|
||||
return new UnpublishResult(UnpublishResultType.FailedCancelledByEvent, evtMsgs, content);
|
||||
@@ -2282,10 +2282,10 @@ namespace Umbraco.Core.Services.Implement
|
||||
foreach (var content in contents.OrderByDescending(x => x.ParentId))
|
||||
{
|
||||
// if it's not trashed yet, and published, we should unpublish
|
||||
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// just raise the event
|
||||
if (content.Trashed == false && content.Published)
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(UnPublished));
|
||||
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(Unpublished));
|
||||
|
||||
// if current content has children, move them to trash
|
||||
var c = content;
|
||||
|
||||
@@ -363,6 +363,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
// write-lock languages to guard against race conds when dealing with default language
|
||||
scope.WriteLock(Constants.Locks.Languages);
|
||||
|
||||
// look for cycles - within write-lock
|
||||
if (language.FallbackLanguageId.HasValue)
|
||||
{
|
||||
var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x);
|
||||
if (!languages.ContainsKey(language.FallbackLanguageId.Value))
|
||||
throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId.Value} which is not a valid language id.");
|
||||
if (CreatesCycle(language, languages))
|
||||
throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle.");
|
||||
}
|
||||
|
||||
var saveEventArgs = new SaveEventArgs<ILanguage>(language);
|
||||
if (scope.Events.DispatchCancelable(SavingLanguage, this, saveEventArgs))
|
||||
{
|
||||
@@ -380,6 +390,20 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreatesCycle(ILanguage language, IDictionary<int, ILanguage> languages)
|
||||
{
|
||||
// a new language is not referenced yet, so cannot be part of a cycle
|
||||
if (!language.HasIdentity) return false;
|
||||
|
||||
var id = language.FallbackLanguageId;
|
||||
while (true) // assuming languages does not already contains a cycle, this must end
|
||||
{
|
||||
if (!id.HasValue) return false; // no fallback means no cycle
|
||||
if (id.Value == language.Id) return true; // back to language = cycle!
|
||||
id = languages[id.Value].FallbackLanguageId; // else keep chaining
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="ILanguage"/> by removing it (but not its usages) from the db
|
||||
/// </summary>
|
||||
@@ -399,8 +423,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return;
|
||||
}
|
||||
|
||||
//NOTE: There isn't any constraints in the db, so possible references aren't deleted
|
||||
|
||||
// NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted
|
||||
_languageRepository.Delete(language);
|
||||
deleteEventArgs.CanCancel = false;
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
private readonly IContentTypeRepository _contentTypeRepository;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private static HttpClient _httpClient;
|
||||
|
||||
public PackagingService(
|
||||
ILogger logger,
|
||||
@@ -1441,7 +1442,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns></returns>
|
||||
public string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId)
|
||||
{
|
||||
using (var httpClient = new HttpClient())
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
//includeHidden = true because we don't care if it's hidden we want to get the file regardless
|
||||
@@ -1449,7 +1449,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
byte[] bytes;
|
||||
try
|
||||
{
|
||||
bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||
if (_httpClient == null)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
bytes = _httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
|
||||
@@ -76,11 +76,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
// reload - cheap, cached
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or slave
|
||||
// than one active server, then role is master or replica
|
||||
regs = _serverRegistrationRepository.GetMany().ToArray();
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or slave
|
||||
// than one active server, then role is master or replica
|
||||
_currentServerRole = regs.Count(x => x.IsActive) > 1
|
||||
? (server.IsMaster ? ServerRole.Master : ServerRole.Replica)
|
||||
: ServerRole.Single;
|
||||
|
||||
@@ -1384,6 +1384,30 @@ namespace Umbraco.Core
|
||||
return idCheckList.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a file name to a friendly name for a content item
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToFriendlyName(this string fileName)
|
||||
{
|
||||
// strip the file extension
|
||||
fileName = fileName.StripFileExtension();
|
||||
|
||||
// underscores and dashes to spaces
|
||||
fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' ');
|
||||
|
||||
// any other conversions ?
|
||||
|
||||
// Pascalcase (to be done last)
|
||||
fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName);
|
||||
|
||||
// Replace multiple consecutive spaces with a single space
|
||||
fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// From: http://stackoverflow.com/a/961504/5018
|
||||
// filters control characters but allows only properly-formed surrogate sequences
|
||||
private static readonly Lazy<Regex> InvalidXmlChars = new Lazy<Regex>(() =>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
Single = 1,
|
||||
|
||||
/// <summary>
|
||||
/// In a multi-servers environment, the server is a slave server.
|
||||
/// In a multi-servers environment, the server is a replica server.
|
||||
/// </summary>
|
||||
Replica = 2,
|
||||
|
||||
|
||||
@@ -362,6 +362,7 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorVariantsModel.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\SuperZero.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\TagsMigration.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\FallbackLanguage.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\UpdateDefaultMandatoryLanguage.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\UserForeignKeys.cs" />
|
||||
<Compile Include="Models\AuditEntry.cs" />
|
||||
@@ -391,12 +392,14 @@
|
||||
<Compile Include="Models\PathValidationExtensions.cs" />
|
||||
<Compile Include="Models\Entities\TreeEntityBase.cs" />
|
||||
<Compile Include="Models\PropertyTagsExtensions.cs" />
|
||||
<Compile Include="Models\PublishedContent\Fallback.cs" />
|
||||
<Compile Include="Models\PublishedContent\NoopPublishedValueFallback.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedCultureInfos.cs" />
|
||||
<Compile Include="Models\PublishedContent\IVariationContextAccessor.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedValueFallback.cs" />
|
||||
<Compile Include="Models\PublishedContent\VariationContext.cs" />
|
||||
<Compile Include="Models\PublishedContent\ThreadCultureVariationContextAccessor.cs" />
|
||||
<Compile Include="Models\PublishedContent\VariationContextAccessorExtensions.cs" />
|
||||
<Compile Include="Persistence\Dtos\AuditEntryDto.cs" />
|
||||
<Compile Include="Persistence\Dtos\ConsentDto.cs" />
|
||||
<Compile Include="Persistence\Dtos\ContentVersionCultureVariationDto.cs" />
|
||||
@@ -727,7 +730,6 @@
|
||||
<Compile Include="Models\PropertyCollection.cs" />
|
||||
<Compile Include="Models\PropertyGroup.cs" />
|
||||
<Compile Include="Models\PropertyGroupCollection.cs" />
|
||||
<Compile Include="Models\PropertyTagChange.cs" />
|
||||
<Compile Include="Models\PropertyType.cs" />
|
||||
<Compile Include="Models\PropertyTypeCollection.cs" />
|
||||
<Compile Include="Models\PublicAccessEntry.cs" />
|
||||
|
||||
Reference in New Issue
Block a user