Merge branch 'v10/dev' into v10/contrib
# Conflicts: # src/Umbraco.Core/EmbeddedResources/Lang/en.xml # src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
This commit is contained in:
@@ -41,17 +41,7 @@ public class DeepCloneableList<T> : List<T>, IDeepCloneable, IRememberBeingDirty
|
||||
// we are cloning once, so create a new list in none mode
|
||||
// and deep clone all items into it
|
||||
var newList = new DeepCloneableList<T>(ListCloneBehavior.None);
|
||||
foreach (T item in this)
|
||||
{
|
||||
if (item is IDeepCloneable dc)
|
||||
{
|
||||
newList.Add((T)dc.DeepClone());
|
||||
}
|
||||
else
|
||||
{
|
||||
newList.Add(item);
|
||||
}
|
||||
}
|
||||
DeepCloneHelper.CloneListItems<DeepCloneableList<T>, T>(this, newList);
|
||||
|
||||
return newList;
|
||||
case ListCloneBehavior.None:
|
||||
@@ -60,17 +50,7 @@ public class DeepCloneableList<T> : List<T>, IDeepCloneable, IRememberBeingDirty
|
||||
case ListCloneBehavior.Always:
|
||||
// always clone to new list
|
||||
var newList2 = new DeepCloneableList<T>(ListCloneBehavior.Always);
|
||||
foreach (T item in this)
|
||||
{
|
||||
if (item is IDeepCloneable dc)
|
||||
{
|
||||
newList2.Add((T)dc.DeepClone());
|
||||
}
|
||||
else
|
||||
{
|
||||
newList2.Add(item);
|
||||
}
|
||||
}
|
||||
DeepCloneHelper.CloneListItems<DeepCloneableList<T>, T>(this, newList2);
|
||||
|
||||
return newList2;
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Collections;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace Umbraco.Cms.Core.Collections;
|
||||
/// Allows clearing all event handlers
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public class EventClearingObservableCollection<TValue> : ObservableCollection<TValue>, INotifyCollectionChanged
|
||||
public class EventClearingObservableCollection<TValue> : ObservableCollection<TValue>, INotifyCollectionChanged, IDeepCloneable
|
||||
{
|
||||
// need to explicitly implement with event accessor syntax in order to override in order to to clear
|
||||
// c# events are weird, they do not behave the same way as other c# things that are 'virtual',
|
||||
@@ -39,4 +40,12 @@ public class EventClearingObservableCollection<TValue> : ObservableCollection<TV
|
||||
/// Clears all event handlers for the <see cref="CollectionChanged" /> event
|
||||
/// </summary>
|
||||
public void ClearCollectionChangedEvents() => _changed = null;
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
var clone = new EventClearingObservableCollection<TValue>();
|
||||
DeepCloneHelper.CloneListItems<EventClearingObservableCollection<TValue>, TValue>(this, clone);
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,7 @@ public class ContentSettings
|
||||
internal const bool StaticHideBackOfficeLogo = false;
|
||||
internal const bool StaticDisableDeleteWhenReferenced = false;
|
||||
internal const bool StaticDisableUnpublishWhenReferenced = false;
|
||||
internal const bool StaticAllowEditInvariantFromNonDefault = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the content notification settings.
|
||||
@@ -242,4 +243,10 @@ public class ContentSettings
|
||||
/// Get or sets the model representing the global content version cleanup policy
|
||||
/// </summary>
|
||||
public ContentVersionCleanupPolicySettings ContentVersionCleanupPolicy { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to allow editing invariant properties from a non-default language variation.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticAllowEditInvariantFromNonDefault)]
|
||||
public bool AllowEditInvariantFromNonDefault { get; set; } = StaticAllowEditInvariantFromNonDefault;
|
||||
}
|
||||
|
||||
@@ -86,9 +86,10 @@ public class SecuritySettings
|
||||
[DefaultValue(StaticUserBypassTwoFactorForExternalLogins)]
|
||||
public bool UserBypassTwoFactorForExternalLogins { get; set; } = StaticUserBypassTwoFactorForExternalLogins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to allow editing invariant properties from a non-default language variation.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticAllowEditInvariantFromNonDefault)]
|
||||
public bool AllowEditInvariantFromNonDefault { get; set; } = StaticAllowEditInvariantFromNonDefault;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to allow editing invariant properties from a non-default language variation.
|
||||
/// </summary>
|
||||
[Obsolete("Use ContentSettings.AllowEditFromInvariant instead")]
|
||||
[DefaultValue(StaticAllowEditInvariantFromNonDefault)]
|
||||
public bool AllowEditInvariantFromNonDefault { get; set; } = StaticAllowEditInvariantFromNonDefault;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,11 @@ public static partial class Constants
|
||||
public const string AreaToken = "area";
|
||||
}
|
||||
|
||||
public static class AttributeRouting
|
||||
{
|
||||
public const string BackOfficeToken = "umbracoBackOffice";
|
||||
}
|
||||
|
||||
public static class EmailTypes
|
||||
{
|
||||
public const string HealthCheck = "HealthCheck";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
@@ -104,6 +105,20 @@ public static partial class UmbracoBuilderExtensions
|
||||
|
||||
builder.Services.Configure<RequestHandlerSettings>(options => options.MergeReplacements(builder.Config));
|
||||
|
||||
// TODO: Remove this in V12
|
||||
// This is to make the move of the AllowEditInvariantFromNonDefault setting from SecuritySettings to ContentSettings backwards compatible
|
||||
// If there is a value in security settings, but no value in content setting we'll use that value, otherwise content settings always wins.
|
||||
builder.Services.Configure<ContentSettings>(settings =>
|
||||
{
|
||||
var securitySettingsValue = builder.Config.GetSection($"{Constants.Configuration.ConfigSecurity}").GetValue<bool?>(nameof(SecuritySettings.AllowEditInvariantFromNonDefault));
|
||||
var contentSettingsValue = builder.Config.GetSection($"{Constants.Configuration.ConfigContent}").GetValue<bool?>(nameof(ContentSettings.AllowEditInvariantFromNonDefault));
|
||||
|
||||
if (securitySettingsValue is not null && contentSettingsValue is null)
|
||||
{
|
||||
settings.AllowEditInvariantFromNonDefault = securitySettingsValue.Value;
|
||||
}
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddUnique<IPropertyTypeUsageService, PropertyTypeUsageService>();
|
||||
Services.AddUnique<IDataTypeUsageService, DataTypeUsageService>();
|
||||
|
||||
Services.AddUnique<ICultureImpactFactory, CultureImpactFactory>();
|
||||
Services.AddUnique<ICultureImpactFactory>(provider => new CultureImpactFactory(provider.GetRequiredService<IOptionsMonitor<ContentSettings>>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1327,11 +1327,8 @@ public static class StringExtensions
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
// From: http://stackoverflow.com/a/35046453/5018
|
||||
public static bool IsFullPath(this string path) =>
|
||||
string.IsNullOrWhiteSpace(path) == false
|
||||
&& path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1
|
||||
&& Path.IsPathRooted(path)
|
||||
&& Path.GetPathRoot(path)?.Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false;
|
||||
// Updated from .NET 2.1+: https://stackoverflow.com/a/58250915
|
||||
public static bool IsFullPath(this string path) => Path.IsPathFullyQualified(path);
|
||||
|
||||
// FORMAT STRINGS
|
||||
|
||||
|
||||
@@ -53,8 +53,7 @@ public abstract class IOHelper : IIOHelper
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
// Check if the path is already mapped - TODO: This should be switched to Path.IsPathFullyQualified once we are on Net Standard 2.1
|
||||
if (IsPathFullyQualified(path))
|
||||
if (path.IsFullPath())
|
||||
{
|
||||
return path;
|
||||
}
|
||||
@@ -231,13 +230,7 @@ public abstract class IOHelper : IIOHelper
|
||||
: CleanFolderResult.Success();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path has a root, and is considered fully qualified for the OS it is on
|
||||
/// See
|
||||
/// https://github.com/dotnet/runtime/blob/30769e8f31b20be10ca26e27ec279cd4e79412b9/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs#L281
|
||||
/// for the .NET Standard 2.1 version of this
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check</param>
|
||||
/// <returns>True if the path is fully qualified, false otherwise</returns>
|
||||
public abstract bool IsPathFullyQualified(string path);
|
||||
[Obsolete("Use Path.IsPathFullyQualified instead. This will be removed in Umbraco 13.")]
|
||||
|
||||
public virtual bool IsPathFullyQualified(string path) => Path.IsPathFullyQualified(path);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ public class IOHelperLinux : IOHelper
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path);
|
||||
|
||||
public override bool PathStartsWith(string path, string root, params char[] separators)
|
||||
{
|
||||
// either it is identical to root,
|
||||
|
||||
@@ -9,8 +9,6 @@ public class IOHelperOSX : IOHelper
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path);
|
||||
|
||||
public override bool PathStartsWith(string path, string root, params char[] separators)
|
||||
{
|
||||
// either it is identical to root,
|
||||
|
||||
@@ -9,35 +9,6 @@ public class IOHelperWindows : IOHelper
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsPathFullyQualified(string path)
|
||||
{
|
||||
// TODO: This implementation is taken from the .NET Standard 2.1 implementation. We should switch to using Path.IsPathFullyQualified once we are on .NET Standard 2.1
|
||||
if (path.Length < 2)
|
||||
{
|
||||
// It isn't fixed, it must be relative. There is no way to specify a fixed
|
||||
// path with one character (or less).
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path[0] == Path.DirectorySeparatorChar || path[0] == Path.AltDirectorySeparatorChar)
|
||||
{
|
||||
// There is no valid way to specify a relative path with two initial slashes or
|
||||
// \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
|
||||
return path[1] == '?' || path[1] == Path.DirectorySeparatorChar ||
|
||||
path[1] == Path.AltDirectorySeparatorChar;
|
||||
}
|
||||
|
||||
// The only way to specify a fixed path that doesn't begin with two slashes
|
||||
// is the drive, colon, slash format- i.e. C:\
|
||||
return path.Length >= 3
|
||||
&& path[1] == Path.VolumeSeparatorChar
|
||||
&& (path[2] == Path.DirectorySeparatorChar || path[2] == Path.AltDirectorySeparatorChar)
|
||||
|
||||
// To match old behavior we'll check the drive character for validity as the path is technically
|
||||
// not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
|
||||
&& ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'));
|
||||
}
|
||||
|
||||
public override bool PathStartsWith(string path, string root, params char[] separators)
|
||||
{
|
||||
// either it is identical to root,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.Collections;
|
||||
using Umbraco.Cms.Core.Collections;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Install.Models;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
@@ -9,6 +9,7 @@ namespace Umbraco.Cms.Core.Install;
|
||||
/// <summary>
|
||||
/// An internal in-memory status tracker for the current installation
|
||||
/// </summary>
|
||||
[Obsolete("This will no longer be used with the new backoffice APi, instead all steps run in one go")]
|
||||
public class InstallStatusTracker
|
||||
{
|
||||
private static ConcurrentHashSet<InstallTrackingItem> _steps = new();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Umbraco.Cms.Core.Install.Models;
|
||||
@@ -10,6 +10,7 @@ namespace Umbraco.Cms.Core.Install.InstallSteps;
|
||||
/// <summary>
|
||||
/// Represents a step in the installation that ensure all the required permissions on files and folders are correct.
|
||||
/// </summary>
|
||||
[Obsolete("Will be replace with a new step with the new backoffice")]
|
||||
[InstallSetupStep(
|
||||
InstallationType.NewInstall | InstallationType.Upgrade,
|
||||
"Permissions",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install.InstallSteps;
|
||||
|
||||
[Obsolete("Will be replace with a new step with the new backoffice")]
|
||||
[InstallSetupStep(
|
||||
InstallationType.NewInstall | InstallationType.Upgrade,
|
||||
"TelemetryIdConfiguration",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Install.Models;
|
||||
using Umbraco.Cms.Core.Semver;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -8,6 +8,7 @@ namespace Umbraco.Cms.Core.Install.InstallSteps
|
||||
/// <summary>
|
||||
/// This step is purely here to show the button to commence the upgrade
|
||||
/// </summary>
|
||||
[Obsolete("Will be replace with a new step with the new backoffice")]
|
||||
[InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, "Upgrading Umbraco to the latest and greatest version.")]
|
||||
public class UpgradeStep : InstallSetupStep<object>
|
||||
{
|
||||
|
||||
@@ -11,6 +11,8 @@ public class DatabaseModel
|
||||
[DataMember(Name = "providerName")]
|
||||
public string? ProviderName { get; set; }
|
||||
|
||||
// TODO: Make this nullable in V11
|
||||
// Server can be null, for instance when installing a SQLite database.
|
||||
[DataMember(Name = "server")]
|
||||
public string Server { get; set; } = null!;
|
||||
|
||||
@@ -18,10 +20,10 @@ public class DatabaseModel
|
||||
public string DatabaseName { get; set; } = null!;
|
||||
|
||||
[DataMember(Name = "login")]
|
||||
public string Login { get; set; } = null!;
|
||||
public string? Login { get; set; }
|
||||
|
||||
[DataMember(Name = "password")]
|
||||
public string Password { get; set; } = null!;
|
||||
public string? Password { get; set; }
|
||||
|
||||
[DataMember(Name = "integratedAuth")]
|
||||
public bool IntegratedAuth { get; set; }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
[Obsolete("Will no longer be required with the new backoffice API")]
|
||||
[DataContract(Name = "installInstructions", Namespace = "")]
|
||||
public class InstallInstructions
|
||||
{
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Returned to the UI for each installation step that is completed
|
||||
/// </summary>
|
||||
[Obsolete("Will no longer be required with the new backoffice API")]
|
||||
[DataContract(Name = "result", Namespace = "")]
|
||||
public class InstallProgressResultModel
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.Install.Models;
|
||||
/// Model containing all the install steps for setting up the UI
|
||||
/// </summary>
|
||||
[DataContract(Name = "installSetup", Namespace = "")]
|
||||
[Obsolete("Will no longer be required with the new backoffice API")]
|
||||
public class InstallSetup
|
||||
{
|
||||
public InstallSetup()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
/// <summary>
|
||||
/// The object returned from each installation step
|
||||
/// </summary>
|
||||
[Obsolete("Will no longer be required with the new backoffice API")]
|
||||
public class InstallSetupResult
|
||||
{
|
||||
public InstallSetupResult()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
@@ -7,6 +7,7 @@ namespace Umbraco.Cms.Core.Install.Models;
|
||||
/// Model to give to the front-end to collect the information for each step
|
||||
/// </summary>
|
||||
[DataContract(Name = "step", Namespace = "")]
|
||||
[Obsolete("Will be replaced with IInstallStep in the new backoffice API")]
|
||||
public abstract class InstallSetupStep<T> : InstallSetupStep
|
||||
{
|
||||
/// <summary>
|
||||
@@ -30,6 +31,7 @@ public abstract class InstallSetupStep<T> : InstallSetupStep
|
||||
}
|
||||
|
||||
[DataContract(Name = "step", Namespace = "")]
|
||||
[Obsolete("Will be replaced with IInstallStep in the new backoffice API")]
|
||||
public abstract class InstallSetupStep
|
||||
{
|
||||
protected InstallSetupStep()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
[Obsolete("Will no longer be required with the use of IInstallStep in the new backoffice API")]
|
||||
public sealed class InstallSetupStepAttribute : Attribute
|
||||
{
|
||||
public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, string view, int serverOrder, string description)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
[Obsolete("Will no longer be required with the new backoffice API")]
|
||||
public class InstallTrackingItem
|
||||
{
|
||||
public InstallTrackingItem(string name, int serverOrder)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
[Obsolete("This will no longer be used with the new backoffice APi, install steps and upgrade steps is instead two different interfaces.")]
|
||||
[Flags]
|
||||
public enum InstallationType
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
[Obsolete("This is no longer used, instead PackageDefinition and InstalledPackage is used")]
|
||||
[DataContract(Name = "package")]
|
||||
public class Package
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
[Obsolete("Will no longer be required with the new backoffice API")]
|
||||
[DataContract(Name = "user", Namespace = "")]
|
||||
public class UserModel
|
||||
{
|
||||
|
||||
@@ -60,15 +60,8 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
|
||||
_contentTypeId = contentType.Id;
|
||||
_properties = properties ?? throw new ArgumentNullException(nameof(properties));
|
||||
_properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes);
|
||||
|
||||
// track all property types on this content type, these can never change during the lifetime of this single instance
|
||||
// there is no real extra memory overhead of doing this since these property types are already cached on this object via the
|
||||
// properties already.
|
||||
AllPropertyTypes = new List<IPropertyType>(contentType.CompositionPropertyTypes);
|
||||
}
|
||||
|
||||
internal IReadOnlyList<IPropertyType> AllPropertyTypes { get; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
public ISimpleContentType ContentType { get; private set; }
|
||||
|
||||
@@ -146,7 +139,6 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
|
||||
base.PerformDeepClone(clone);
|
||||
|
||||
var clonedContent = (ContentBase)clone;
|
||||
|
||||
// Need to manually clone this since it's not settable
|
||||
clonedContent.ContentType = ContentType;
|
||||
|
||||
|
||||
@@ -212,4 +212,16 @@ public static class DeepCloneHelper
|
||||
|
||||
public bool IsList => GenericListType != null;
|
||||
}
|
||||
|
||||
public static void CloneListItems<TList, TEntity>(TList source, TList target)
|
||||
where TList : ICollection<TEntity>
|
||||
{
|
||||
target.Clear();
|
||||
foreach (TEntity entity in source)
|
||||
{
|
||||
target.Add(entity is IDeepCloneable deepCloneableEntity
|
||||
? (TEntity)deepCloneableEntity.DeepClone()
|
||||
: entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ public abstract class BeingDirtyBase : IRememberBeingDirty
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void ClearPropertyChangedEvents() => PropertyChanged = null;
|
||||
|
||||
/// <summary>
|
||||
/// Registers that a property has changed.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ContentVariantMapper
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IUserService _userService;
|
||||
private SecuritySettings _securitySettings;
|
||||
private ContentSettings _contentSettings;
|
||||
|
||||
public ContentVariantMapper(
|
||||
ILocalizationService localizationService,
|
||||
@@ -28,17 +28,36 @@ public class ContentVariantMapper
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IContentService contentService,
|
||||
IUserService userService,
|
||||
IOptionsMonitor<SecuritySettings> securitySettings)
|
||||
IOptionsMonitor<ContentSettings> contentSettings)
|
||||
{
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_contentService = contentService;
|
||||
_userService = userService;
|
||||
_securitySettings = securitySettings.CurrentValue;
|
||||
securitySettings.OnChange(settings => _securitySettings = settings);
|
||||
_contentSettings = contentSettings.CurrentValue;
|
||||
contentSettings.OnChange(settings => _contentSettings = settings);
|
||||
}
|
||||
|
||||
[Obsolete("Use constructor that takes all parameters instead")]
|
||||
public ContentVariantMapper(
|
||||
ILocalizationService localizationService,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IContentService contentService,
|
||||
IUserService userService,
|
||||
IOptionsMonitor<SecuritySettings> securitySettings)
|
||||
: this(
|
||||
localizationService,
|
||||
localizedTextService,
|
||||
backOfficeSecurityAccessor,
|
||||
contentService,
|
||||
userService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<ContentSettings>>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use constructor that takes all parameters instead")]
|
||||
public ContentVariantMapper(ILocalizationService localizationService, ILocalizedTextService localizedTextService)
|
||||
: this(
|
||||
localizationService,
|
||||
@@ -244,7 +263,7 @@ public class ContentVariantMapper
|
||||
if (variantDisplay.Language is null)
|
||||
{
|
||||
var defaultLanguageId = _localizationService.GetDefaultLanguageId();
|
||||
if (_securitySettings.AllowEditInvariantFromNonDefault || (defaultLanguageId.HasValue && group.HasAccessToLanguage(defaultLanguageId.Value)))
|
||||
if (_contentSettings.AllowEditInvariantFromNonDefault || (defaultLanguageId.HasValue && group.HasAccessToLanguage(defaultLanguageId.Value)))
|
||||
{
|
||||
hasAccess = true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.Mapping;
|
||||
|
||||
@@ -14,8 +16,9 @@ public class DictionaryMapDefinition : IMapDefinition
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
[Obsolete("Use the constructor with the CommonMapper")]
|
||||
public DictionaryMapDefinition(ILocalizationService localizationService) =>
|
||||
_localizationService = localizationService;
|
||||
public DictionaryMapDefinition(ILocalizationService localizationService) : this(localizationService, StaticServiceProvider.Instance.GetRequiredService<CommonMapper>())
|
||||
{
|
||||
}
|
||||
|
||||
public DictionaryMapDefinition(ILocalizationService localizationService, CommonMapper commonMapper)
|
||||
{
|
||||
|
||||
@@ -283,12 +283,7 @@ public class PropertyType : EntityBase, IPropertyType, IEquatable<PropertyType>
|
||||
base.PerformDeepClone(clone);
|
||||
|
||||
var clonedEntity = (PropertyType)clone;
|
||||
|
||||
// need to manually assign the Lazy value as it will not be automatically mapped
|
||||
if (PropertyGroupId != null)
|
||||
{
|
||||
clonedEntity._propertyGroupId = new Lazy<int>(() => PropertyGroupId.Value);
|
||||
}
|
||||
clonedEntity.ClearPropertyChangedEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Umbraco.Cms.Core.Models;
|
||||
public class PublicAccessEntry : EntityBase
|
||||
{
|
||||
private readonly List<Guid> _removedRules = new();
|
||||
private readonly EventClearingObservableCollection<PublicAccessRule> _ruleCollection;
|
||||
private EventClearingObservableCollection<PublicAccessRule> _ruleCollection;
|
||||
private int _loginNodeId;
|
||||
private int _noAccessNodeId;
|
||||
private int _protectedNodeId;
|
||||
@@ -144,11 +144,13 @@ public class PublicAccessEntry : EntityBase
|
||||
|
||||
var cloneEntity = (PublicAccessEntry)clone;
|
||||
|
||||
if (cloneEntity._ruleCollection != null)
|
||||
{
|
||||
cloneEntity._ruleCollection.ClearCollectionChangedEvents(); // clear this event handler if any
|
||||
cloneEntity._ruleCollection.CollectionChanged +=
|
||||
cloneEntity.RuleCollection_CollectionChanged; // re-assign correct event handler
|
||||
}
|
||||
// clear this event handler if any
|
||||
cloneEntity._ruleCollection.ClearCollectionChangedEvents();
|
||||
|
||||
// clone the rule collection explicitly
|
||||
cloneEntity._ruleCollection = (EventClearingObservableCollection<PublicAccessRule>)_ruleCollection.DeepClone();
|
||||
|
||||
// re-assign correct event handler
|
||||
cloneEntity._ruleCollection.CollectionChanged += cloneEntity.RuleCollection_CollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
"Content Picker",
|
||||
"contentpicker",
|
||||
ValueType = ValueTypes.String,
|
||||
Group = Constants.PropertyEditors.Groups.Pickers)]
|
||||
Group = Constants.PropertyEditors.Groups.Pickers,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class ContentPickerPropertyEditor : DataEditor
|
||||
{
|
||||
private readonly IEditorConfigurationParser _editorConfigurationParser;
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
[DataContract]
|
||||
public class DataEditor : IDataEditor
|
||||
{
|
||||
private readonly bool _canReuseValueEditor;
|
||||
private IDataValueEditor? _reusableValueEditor;
|
||||
private IDictionary<string, object>? _defaultConfiguration;
|
||||
|
||||
/// <summary>
|
||||
@@ -48,6 +50,8 @@ public class DataEditor : IDataEditor
|
||||
Icon = Attribute.Icon;
|
||||
Group = Attribute.Group;
|
||||
IsDeprecated = Attribute.IsDeprecated;
|
||||
|
||||
_canReuseValueEditor = Attribute.ValueEditorIsReusable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -118,18 +122,14 @@ public class DataEditor : IDataEditor
|
||||
/// instance is returned. Otherwise, a new instance is created by CreateValueEditor.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The instance created by CreateValueEditor is not cached, i.e.
|
||||
/// a new instance is created each time the property value is retrieved. The
|
||||
/// property editor is a singleton, and the value editor cannot be a singleton
|
||||
/// since it depends on the datatype configuration.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Technically, it could be cached by datatype but let's keep things
|
||||
/// simple enough for now.
|
||||
/// The instance created by CreateValueEditor is cached if allowed by the DataEditor
|
||||
/// attribute (<see cref="DataEditorAttribute.ValueEditorIsReusable"/> == true).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
// TODO: point of that one? shouldn't we always configure?
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor
|
||||
?? (_canReuseValueEditor
|
||||
? _reusableValueEditor ??= CreateValueEditor()
|
||||
: CreateValueEditor());
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
|
||||
@@ -178,4 +178,10 @@ public sealed class DataEditorAttribute : Attribute
|
||||
/// </summary>
|
||||
/// <remarks>A deprecated editor is still supported but not proposed in the UI.</remarks>
|
||||
public bool IsDeprecated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the value editor can be reused (cached).
|
||||
/// </summary>
|
||||
/// <remarks>While most value editors can be reused, complex editors (e.g. block based editors) might not be applicable for reuse.</remarks>
|
||||
public bool ValueEditorIsReusable { get; set; }
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
EditorType.PropertyValue | EditorType.MacroParameter,
|
||||
"Decimal",
|
||||
"decimal",
|
||||
ValueType = ValueTypes.Decimal)]
|
||||
ValueType = ValueTypes.Decimal,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class DecimalPropertyEditor : DataEditor
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
"Eye Dropper Color Picker",
|
||||
"eyedropper",
|
||||
Icon = "icon-colorpicker",
|
||||
Group = Constants.PropertyEditors.Groups.Pickers)]
|
||||
Group = Constants.PropertyEditors.Groups.Pickers,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class EyeDropperColorPickerPropertyEditor : DataEditor
|
||||
{
|
||||
private readonly IEditorConfigurationParser _editorConfigurationParser;
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
EditorType.PropertyValue | EditorType.MacroParameter,
|
||||
"Numeric",
|
||||
"integer",
|
||||
ValueType = ValueTypes.Integer)]
|
||||
ValueType = ValueTypes.Integer,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class IntegerPropertyEditor : DataEditor
|
||||
{
|
||||
public IntegerPropertyEditor(
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
Constants.PropertyEditors.Aliases.Label,
|
||||
"Label",
|
||||
"readonlyvalue",
|
||||
Icon = "icon-readonly")]
|
||||
Icon = "icon-readonly",
|
||||
ValueEditorIsReusable = true)]
|
||||
public class LabelPropertyEditor : DataEditor
|
||||
{
|
||||
private readonly IEditorConfigurationParser _editorConfigurationParser;
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
"markdowneditor",
|
||||
ValueType = ValueTypes.Text,
|
||||
Group = Constants.PropertyEditors.Groups.RichContent,
|
||||
Icon = "icon-code")]
|
||||
Icon = "icon-code",
|
||||
ValueEditorIsReusable = true)]
|
||||
public class MarkdownPropertyEditor : DataEditor
|
||||
{
|
||||
private readonly IEditorConfigurationParser _editorConfigurationParser;
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
"membergrouppicker",
|
||||
ValueType = ValueTypes.Text,
|
||||
Group = Constants.PropertyEditors.Groups.People,
|
||||
Icon = Constants.Icons.MemberGroup)]
|
||||
Icon = Constants.Icons.MemberGroup,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class MemberGroupPickerPropertyEditor : DataEditor
|
||||
{
|
||||
public MemberGroupPickerPropertyEditor(
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
"memberpicker",
|
||||
ValueType = ValueTypes.String,
|
||||
Group = Constants.PropertyEditors.Groups.People,
|
||||
Icon = Constants.Icons.Member)]
|
||||
Icon = Constants.Icons.Member,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class MemberPickerPropertyEditor : DataEditor
|
||||
{
|
||||
public MemberPickerPropertyEditor(
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
"userpicker",
|
||||
ValueType = ValueTypes.Integer,
|
||||
Group = Constants.PropertyEditors.Groups.People,
|
||||
Icon = Constants.Icons.User)]
|
||||
Icon = Constants.Icons.User,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class UserPickerPropertyEditor : DataEditor
|
||||
{
|
||||
public UserPickerPropertyEditor(
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public class CultureImpactFactory : ICultureImpactFactory
|
||||
{
|
||||
private SecuritySettings _securitySettings;
|
||||
private ContentSettings _contentSettings;
|
||||
|
||||
public CultureImpactFactory(IOptionsMonitor<SecuritySettings> securitySettings)
|
||||
public CultureImpactFactory(IOptionsMonitor<ContentSettings> contentSettings)
|
||||
{
|
||||
_securitySettings = securitySettings.CurrentValue;
|
||||
_contentSettings = contentSettings.CurrentValue;
|
||||
|
||||
securitySettings.OnChange(x => _securitySettings = x);
|
||||
contentSettings.OnChange(x => _contentSettings = x);
|
||||
}
|
||||
|
||||
[Obsolete("Use constructor that takes IOptionsMonitor<SecuritySettings> instead. Scheduled for removal in V12")]
|
||||
public CultureImpactFactory(IOptionsMonitor<SecuritySettings> securitySettings)
|
||||
: this(StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<ContentSettings>>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CultureImpact? Create(string? culture, bool isDefault, IContent content)
|
||||
{
|
||||
TryCreate(culture, isDefault, content.ContentType.Variations, true, _securitySettings.AllowEditInvariantFromNonDefault, out CultureImpact? impact);
|
||||
TryCreate(culture, isDefault, content.ContentType.Variations, true, _contentSettings.AllowEditInvariantFromNonDefault, out CultureImpact? impact);
|
||||
|
||||
return impact;
|
||||
}
|
||||
@@ -48,7 +56,7 @@ public class CultureImpactFactory : ICultureImpactFactory
|
||||
throw new ArgumentException("Culture \"*\" is not explicit.");
|
||||
}
|
||||
|
||||
return new CultureImpact(culture, isDefault, _securitySettings.AllowEditInvariantFromNonDefault);
|
||||
return new CultureImpact(culture, isDefault, _contentSettings.AllowEditInvariantFromNonDefault);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
Reference in New Issue
Block a user