Merge remote-tracking branch 'origin/v10/dev' into v11/dev

This commit is contained in:
Bjarke Berg
2023-02-15 14:06:39 +01:00
27 changed files with 613 additions and 55 deletions

View File

@@ -22,6 +22,9 @@ public class SecuritySettings
internal const string StaticAllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\";
internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60;
internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60;
/// <summary>
/// Gets or sets a value indicating whether to keep the user logged in.
/// </summary>
@@ -86,6 +89,18 @@ public class SecuritySettings
[DefaultValue(StaticUserBypassTwoFactorForExternalLogins)]
public bool UserBypassTwoFactorForExternalLogins { get; set; } = StaticUserBypassTwoFactorForExternalLogins;
/// <summary>
/// Gets or sets a value for how long (in minutes) a member is locked out when a lockout occurs.
/// </summary>
[DefaultValue(StaticMemberDefaultLockoutTimeInMinutes)]
public int MemberDefaultLockoutTimeInMinutes { get; set; } = StaticMemberDefaultLockoutTimeInMinutes;
/// <summary>
/// Gets or sets a value for how long (in minutes) a user is locked out when a lockout occurs.
/// </summary>
[DefaultValue(StaticUserDefaultLockoutTimeInMinutes)]
public int UserDefaultLockoutTimeInMinutes { get; set; } = StaticUserDefaultLockoutTimeInMinutes;
/// <summary>
/// Gets or sets a value indicating whether to allow editing invariant properties from a non-default language variation.
/// </summary>

View File

@@ -0,0 +1,5 @@
namespace Umbraco.Cms.Core.PropertyEditors;
public interface IBlockValuePropertyIndexValueFactory : IPropertyIndexValueFactory
{
}

View File

@@ -0,0 +1,5 @@
namespace Umbraco.Cms.Core.PropertyEditors;
public interface INestedContentPropertyIndexValueFactory : IPropertyIndexValueFactory
{
}

View File

@@ -0,0 +1,5 @@
namespace Umbraco.Cms.Core.PropertyEditors;
public interface ITagPropertyIndexValueFactory : IPropertyIndexValueFactory
{
}

View File

@@ -0,0 +1,84 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Abstract base for property index value factories where the value is json.
/// </summary>
/// <typeparam name="TSerialized">The type to deserialize the json to.</typeparam>
public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IPropertyIndexValueFactory
{
private readonly IJsonSerializer _jsonSerializer;
/// <summary>
/// Constructor for the JsonPropertyIndexValueFactoryBase.
/// </summary>
protected JsonPropertyIndexValueFactoryBase(IJsonSerializer jsonSerializer)
{
_jsonSerializer = jsonSerializer;
}
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(
IProperty property,
string? culture,
string? segment,
bool published)
{
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
var propertyValue = property.GetValue(culture, segment, published);
// If there is a value, it's a string and it's detected as json.
if (propertyValue is string rawValue && rawValue.DetectIsJson())
{
try
{
TSerialized? deserializedPropertyValue = _jsonSerializer.Deserialize<TSerialized>(rawValue);
if (deserializedPropertyValue is null)
{
return result;
}
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published));
}
catch (InvalidCastException)
{
// Swallow...on purpose, there's a chance that this isn't the json format we are looking for
// and we don't want that to affect the website.
}
catch (ArgumentException)
{
// Swallow on purpose to prevent this error:
// Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject.
}
}
result.AddRange(HandleResume(result, property, culture, segment, published));
return result;
}
/// <summary>
/// Method to return a list of resume of the content. By default this returns an empty list
/// </summary>
protected virtual IEnumerable<KeyValuePair<string, IEnumerable<object?>>> HandleResume(
List<KeyValuePair<string, IEnumerable<object?>>> result,
IProperty property,
string? culture,
string? segment,
bool published) => Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
/// <summary>
/// Method that handle the deserialized object.
/// </summary>
protected abstract IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
TSerialized deserializedPropertyValue,
IProperty property,
string? culture,
string? segment,
bool published);
}

View File

@@ -0,0 +1,12 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Property Index Valye Factory that do not index anything.
/// </summary>
public class NoopPropertyIndexValueFactory : IPropertyIndexValueFactory
{
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) => Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
}

View File

@@ -0,0 +1,21 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.PropertyEditors;
public class TagPropertyIndexValueFactory : JsonPropertyIndexValueFactoryBase<string[]>, ITagPropertyIndexValueFactory
{
public TagPropertyIndexValueFactory(IJsonSerializer jsonSerializer) : base(jsonSerializer)
{
}
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
string[] deserializedPropertyValue,
IProperty property,
string? culture,
string? segment,
bool published)
{
yield return new KeyValuePair<string, IEnumerable<object?>>(property.Alias, deserializedPropertyValue);
}
}

View File

@@ -76,7 +76,8 @@ public class NotificationService : INotificationService
Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody)
{
var entitiesL = entities.ToList();
// sort the entities explicitly by path to handle notification inheritance (see comment below)
var entitiesL = entities.OrderBy(entity => entity.Path).ToList();
// exit if there are no entities
if (entitiesL.Count == 0)
@@ -84,11 +85,11 @@ public class NotificationService : INotificationService
return;
}
// put all entity's paths into a list with the same indices
var paths = entitiesL.Select(x =>
x.Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture))
.ToArray())
.ToArray();
// create a dictionary of entity paths by entity ID
var pathsByEntityId = entitiesL.ToDictionary(
entity => entity.Id,
entity => entity.Path.Split(Constants.CharArrays.Comma)
.Select(s => int.Parse(s, CultureInfo.InvariantCulture)).ToArray());
// lazily get versions
var prevVersionDictionary = new Dictionary<int, IContentBase?>();
@@ -106,47 +107,32 @@ public class NotificationService : INotificationService
break;
}
var i = 0;
foreach (IUser user in users)
{
// continue if there's no notification for this user
if (notifications[i].UserId != user.Id)
Notification[] userNotifications = notifications.Where(n => n.UserId == user.Id).ToArray();
foreach (Notification notification in userNotifications)
{
continue; // next user
}
// notifications are inherited down the tree - find the topmost entity
// relevant to this notification (entity list is sorted by path)
IContent? entityForNotification = entitiesL
.FirstOrDefault(entity =>
pathsByEntityId.TryGetValue(entity.Id, out var path) &&
path.Contains(notification.EntityId));
for (var j = 0; j < entitiesL.Count; j++)
{
IContent content = entitiesL[j];
var path = paths[j];
// test if the notification applies to the path ie to this entity
if (path.Contains(notifications[i].EntityId) == false)
if (entityForNotification == null)
{
continue; // next entity
continue;
}
if (prevVersionDictionary.ContainsKey(content.Id) == false)
if (prevVersionDictionary.ContainsKey(entityForNotification.Id) == false)
{
prevVersionDictionary[content.Id] = GetPreviousVersion(content.Id);
prevVersionDictionary[entityForNotification.Id] = GetPreviousVersion(entityForNotification.Id);
}
// queue notification
NotificationRequest req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody);
NotificationRequest req = CreateNotificationRequest(operatingUser, user, entityForNotification, prevVersionDictionary[entityForNotification.Id], actionName, siteUri, createSubject, createBody);
Enqueue(req);
}
// skip other notifications for this user, essentially this means moving i to the next index of notifications
// for the next user.
do
{
i++;
}
while (i < notifications.Count && notifications[i].UserId == user.Id);
if (i >= notifications.Count)
{
break; // break if no more notifications
break;
}
}

View File

@@ -218,6 +218,19 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
builder.Services.AddTransient<IFireAndForgetRunner, FireAndForgetRunner>();
builder.AddPropertyIndexValueFactories();
return builder;
}
public static IUmbracoBuilder AddPropertyIndexValueFactories(this IUmbracoBuilder builder)
{
builder.Services.AddSingleton<IBlockValuePropertyIndexValueFactory, BlockValuePropertyIndexValueFactory>();
builder.Services.AddSingleton<INestedContentPropertyIndexValueFactory, NestedContentPropertyIndexValueFactory>();
builder.Services.AddSingleton<ITagPropertyIndexValueFactory, TagPropertyIndexValueFactory>();
return builder;
}

View File

@@ -1,6 +1,9 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.PropertyEditors;
// Scheduled for removal in v12
@@ -11,11 +14,26 @@ public abstract class BlockEditorPropertyEditor : BlockListPropertyEditorBase
public const string ContentTypeKeyPropertyKey = "contentTypeKey";
public const string UdiPropertyKey = "udi";
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
protected BlockEditorPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
PropertyEditorCollection propertyEditors)
: base(dataValueEditorFactory) =>
: this(
dataValueEditorFactory,
propertyEditors,
StaticServiceProvider.Instance.GetRequiredService<IBlockValuePropertyIndexValueFactory>())
{
}
protected BlockEditorPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
PropertyEditorCollection propertyEditors,
IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
: base(dataValueEditorFactory, blockValuePropertyIndexValueFactory)
{
PropertyEditors = propertyEditors;
}
private PropertyEditorCollection PropertyEditors { get; }
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -19,11 +21,25 @@ public class BlockGridPropertyEditor : BlockGridPropertyEditorBase
{
private readonly IIOHelper _ioHelper;
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
public BlockGridPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IIOHelper ioHelper)
: base(dataValueEditorFactory) =>
: this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService<IBlockValuePropertyIndexValueFactory>())
{
}
public BlockGridPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IIOHelper ioHelper,
IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
: base(dataValueEditorFactory, blockValuePropertyIndexValueFactory)
{
_ioHelper = ioHelper;
}
#region Pre Value Editor

View File

@@ -2,6 +2,7 @@
// See LICENSE for more details.
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
@@ -9,6 +10,7 @@ using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
using BlockGridAreaConfiguration = Umbraco.Cms.Core.PropertyEditors.BlockGridConfiguration.BlockGridAreaConfiguration;
@@ -19,9 +21,24 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// </summary>
public abstract class BlockGridPropertyEditorBase : DataEditor
{
private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory;
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
protected BlockGridPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory)
: base(dataValueEditorFactory) =>
: this(dataValueEditorFactory, StaticServiceProvider.Instance.GetRequiredService<IBlockValuePropertyIndexValueFactory>())
{
}
protected BlockGridPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
: base(dataValueEditorFactory)
{
_blockValuePropertyIndexValueFactory = blockValuePropertyIndexValueFactory;
SupportsReadOnly = true;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory;
#region Value Editor

View File

@@ -25,7 +25,7 @@ public class BlockListPropertyEditor : BlockEditorPropertyEditor
private readonly IIOHelper _ioHelper;
// Scheduled for removal in v12
[Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")]
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
public BlockListPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
PropertyEditorCollection propertyEditors,
@@ -34,12 +34,29 @@ public class BlockListPropertyEditor : BlockEditorPropertyEditor
{
}
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
public BlockListPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
PropertyEditorCollection propertyEditors,
IIOHelper ioHelper,
IEditorConfigurationParser editorConfigurationParser)
: base(dataValueEditorFactory, propertyEditors)
: this(
dataValueEditorFactory,
propertyEditors,
ioHelper,
editorConfigurationParser,
StaticServiceProvider.Instance.GetRequiredService<IBlockValuePropertyIndexValueFactory>())
{
}
public BlockListPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
PropertyEditorCollection propertyEditors,
IIOHelper ioHelper,
IEditorConfigurationParser editorConfigurationParser,
IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
: base(dataValueEditorFactory, propertyEditors, blockValuePropertyIndexValueFactory)
{
_ioHelper = ioHelper;
_editorConfigurationParser = editorConfigurationParser;

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
@@ -6,6 +7,7 @@ using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -14,9 +16,25 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// </summary>
public abstract class BlockListPropertyEditorBase : DataEditor
{
private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory;
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory)
: base(dataValueEditorFactory) =>
: this(dataValueEditorFactory, StaticServiceProvider.Instance.GetRequiredService<IBlockValuePropertyIndexValueFactory>())
{
}
protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
: base(dataValueEditorFactory)
{
_blockValuePropertyIndexValueFactory = blockValuePropertyIndexValueFactory;
SupportsReadOnly = true;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory;
#region Value Editor

View File

@@ -0,0 +1,35 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Core.PropertyEditors;
internal sealed class BlockValuePropertyIndexValueFactory :
NestedPropertyIndexValueFactoryBase<BlockValue, BlockItemData>,
IBlockValuePropertyIndexValueFactory
{
private readonly IContentTypeService _contentTypeService;
public BlockValuePropertyIndexValueFactory(
PropertyEditorCollection propertyEditorCollection,
IContentTypeService contentTypeService,
IJsonSerializer jsonSerializer)
: base(propertyEditorCollection, jsonSerializer)
{
_contentTypeService = contentTypeService;
}
protected override IContentType? GetContentTypeOfNestedItem(BlockItemData input) =>
_contentTypeService.Get(input.ContentTypeKey);
protected override IDictionary<string, object?> GetRawProperty(BlockItemData blockItemData) =>
blockItemData.RawPropertyValues;
protected override IEnumerable<BlockItemData> GetDataItems(BlockValue input) => input.ContentData;
}

View File

@@ -45,6 +45,9 @@ public class ColorPickerPropertyEditor : DataEditor
SupportsReadOnly = true;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory();
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() =>
new ColorPickerConfigurationEditor(_ioHelper, _jsonSerializer, _editorConfigurationParser);

View File

@@ -97,6 +97,8 @@ public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator,
SupportsReadOnly = true;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory();
public bool TryGetMediaPath(string? propertyEditorAlias, object? value, out string? mediaPath)
{
if (propertyEditorAlias == Alias &&

View File

@@ -58,6 +58,8 @@ public class MediaPicker3PropertyEditor : DataEditor
SupportsReadOnly = true;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory();
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() =>
new MediaPicker3ConfigurationEditor(_ioHelper, _editorConfigurationParser);
@@ -66,6 +68,8 @@ public class MediaPicker3PropertyEditor : DataEditor
protected override IDataValueEditor CreateValueEditor() =>
DataValueEditorFactory.Create<MediaPicker3PropertyValueEditor>(Attribute!);
internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference
{
private readonly IDataTypeService _dataTypeService;

View File

@@ -33,9 +33,9 @@ public class NestedContentPropertyEditor : DataEditor
public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";
private readonly IEditorConfigurationParser _editorConfigurationParser;
private readonly IIOHelper _ioHelper;
private readonly INestedContentPropertyIndexValueFactory _nestedContentPropertyIndexValueFactory;
// Scheduled for removal in v12
[Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")]
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 12.")]
public NestedContentPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IIOHelper ioHelper)
@@ -43,17 +43,35 @@ public class NestedContentPropertyEditor : DataEditor
{
}
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
public NestedContentPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IIOHelper ioHelper,
IEditorConfigurationParser editorConfigurationParser)
: this(
dataValueEditorFactory,
ioHelper,
editorConfigurationParser,
StaticServiceProvider.Instance.GetRequiredService<INestedContentPropertyIndexValueFactory>())
{
}
public NestedContentPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IIOHelper ioHelper,
IEditorConfigurationParser editorConfigurationParser,
INestedContentPropertyIndexValueFactory nestedContentPropertyIndexValueFactory)
: base(dataValueEditorFactory)
{
_ioHelper = ioHelper;
_editorConfigurationParser = editorConfigurationParser;
_nestedContentPropertyIndexValueFactory = nestedContentPropertyIndexValueFactory;
SupportsReadOnly = true;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _nestedContentPropertyIndexValueFactory;
#region Pre Value Editor
protected override IConfigurationEditor CreateConfigurationEditor() =>

View File

@@ -0,0 +1,37 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Core.PropertyEditors;
internal sealed class NestedContentPropertyIndexValueFactory
: NestedPropertyIndexValueFactoryBase<
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue[],
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue>,
INestedContentPropertyIndexValueFactory
{
private readonly IContentTypeService _contentTypeService;
public NestedContentPropertyIndexValueFactory(
PropertyEditorCollection propertyEditorCollection,
IContentTypeService contentTypeService,
IJsonSerializer jsonSerializer) : base(propertyEditorCollection, jsonSerializer)
{
_contentTypeService = contentTypeService;
}
protected override IContentType? GetContentTypeOfNestedItem(
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue input)
=> _contentTypeService.Get(input.ContentTypeAlias);
protected override IDictionary<string, object?> GetRawProperty(
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue nestedContentRowValue) =>
nestedContentRowValue.RawPropertyValues;
protected override IEnumerable<NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue> GetDataItems(
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue[] input) => input;
}

View File

@@ -0,0 +1,181 @@
using System.Text;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Infrastructure.Examine;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
internal abstract class NestedPropertyIndexValueFactoryBase<TSerialized, TItem> : JsonPropertyIndexValueFactoryBase<TSerialized>
{
private readonly PropertyEditorCollection _propertyEditorCollection;
protected NestedPropertyIndexValueFactoryBase(
PropertyEditorCollection propertyEditorCollection,
IJsonSerializer jsonSerializer)
: base(jsonSerializer)
{
_propertyEditorCollection = propertyEditorCollection;
}
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
TSerialized deserializedPropertyValue,
IProperty property,
string? culture,
string? segment,
bool published)
{
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
foreach (TItem nestedContentRowValue in GetDataItems(deserializedPropertyValue))
{
IContentType? contentType = GetContentTypeOfNestedItem(nestedContentRowValue);
if (contentType is null)
{
continue;
}
var propertyTypeDictionary =
contentType
.PropertyGroups
.SelectMany(x => x.PropertyTypes!)
.ToDictionary(x => x.Alias);
result.AddRange(GetNestedResults(
property.Alias,
culture,
segment,
published,
propertyTypeDictionary,
nestedContentRowValue));
}
return RenameKeysToEnsureRawSegmentsIsAPrefix(result);
}
/// <summary>
/// Rename keys that count the RAW-constant, to ensure the RAW-constant is a prefix.
/// </summary>
private IEnumerable<KeyValuePair<string, IEnumerable<object?>>> RenameKeysToEnsureRawSegmentsIsAPrefix(
List<KeyValuePair<string, IEnumerable<object?>>> indexContent)
{
foreach (KeyValuePair<string, IEnumerable<object?>> indexedKeyValuePair in indexContent)
{
// Tests if key includes the RawFieldPrefix and it is not in the start
if (indexedKeyValuePair.Key.Substring(1).Contains(UmbracoExamineFieldNames.RawFieldPrefix))
{
var newKey = UmbracoExamineFieldNames.RawFieldPrefix +
indexedKeyValuePair.Key.Replace(UmbracoExamineFieldNames.RawFieldPrefix, string.Empty);
yield return new KeyValuePair<string, IEnumerable<object?>>(newKey, indexedKeyValuePair.Value);
}
else
{
yield return indexedKeyValuePair;
}
}
}
/// <summary>
/// Gets the content type using the nested item.
/// </summary>
protected abstract IContentType? GetContentTypeOfNestedItem(TItem nestedItem);
/// <summary>
/// Gets the raw data from a nested item.
/// </summary>
protected abstract IDictionary<string, object?> GetRawProperty(TItem nestedItem);
/// <summary>
/// Get the data times of a parent item. E.g. block list have contentData.
/// </summary>
protected abstract IEnumerable<TItem> GetDataItems(TSerialized input);
/// <summary>
/// Index a key with the name of the property, using the relevant content of all the children.
/// </summary>
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> HandleResume(
List<KeyValuePair<string, IEnumerable<object?>>> indexedContent,
IProperty property,
string? culture,
string? segment,
bool published)
{
yield return new KeyValuePair<string, IEnumerable<object?>>(
property.Alias,
GetResumeFromAllContent(indexedContent).Yield());
}
/// <summary>
/// Gets a resume as string of all the content in this nested type.
/// </summary>
/// <param name="indexedContent">All the indexed content for this property.</param>
/// <returns>the string with all relevant content from </returns>
private static string GetResumeFromAllContent(List<KeyValuePair<string, IEnumerable<object?>>> indexedContent)
{
var stringBuilder = new StringBuilder();
foreach ((var indexKey, IEnumerable<object?>? indexedValue) in indexedContent)
{
// Ignore Raw fields
if (indexKey.Contains(UmbracoExamineFieldNames.RawFieldPrefix))
{
continue;
}
foreach (var value in indexedValue)
{
if (value is not null)
{
stringBuilder.AppendLine(value.ToString());
}
}
}
return stringBuilder.ToString();
}
/// <summary>
/// Gets the content to index for the nested type. E.g. Block list, Nested Content, etc..
/// </summary>
private IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetNestedResults(
string keyPrefix,
string? culture,
string? segment,
bool published,
IDictionary<string, IPropertyType> propertyTypeDictionary,
TItem nestedContentRowValue)
{
var blockIndex = 0;
foreach ((var propertyAlias, var propertyValue) in GetRawProperty(nestedContentRowValue))
{
if (propertyTypeDictionary.TryGetValue(propertyAlias, out IPropertyType? propertyType))
{
IProperty subProperty = new Property(propertyType);
subProperty.SetValue(propertyValue, culture, segment);
if (published)
{
subProperty.PublishValues(culture, segment ?? "*");
}
IDataEditor? editor = _propertyEditorCollection[propertyType.PropertyEditorAlias];
if (editor is null)
{
continue;
}
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> indexValues =
editor.PropertyIndexValueFactory.GetIndexValues(subProperty, culture, segment, published);
foreach ((var nestedAlias, IEnumerable<object?> nestedValue) in indexValues)
{
yield return new KeyValuePair<string, IEnumerable<object?>>(
$"{keyPrefix}.items[{blockIndex}].{nestedAlias}", nestedValue!);
}
}
blockIndex++;
}
}
}

View File

@@ -29,12 +29,13 @@ namespace Umbraco.Cms.Core.PropertyEditors;
public class TagsPropertyEditor : DataEditor
{
private readonly IEditorConfigurationParser _editorConfigurationParser;
private readonly ITagPropertyIndexValueFactory _tagPropertyIndexValueFactory;
private readonly IIOHelper _ioHelper;
private readonly ILocalizedTextService _localizedTextService;
private readonly ManifestValueValidatorCollection _validators;
// Scheduled for removal in v12
[Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")]
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
public TagsPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
ManifestValueValidatorCollection validators,
@@ -45,24 +46,48 @@ public class TagsPropertyEditor : DataEditor
validators,
ioHelper,
localizedTextService,
StaticServiceProvider.Instance.GetRequiredService<IEditorConfigurationParser>())
StaticServiceProvider.Instance.GetRequiredService<IEditorConfigurationParser>(),
StaticServiceProvider.Instance.GetRequiredService<ITagPropertyIndexValueFactory>())
{
}
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
public TagsPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
ManifestValueValidatorCollection validators,
IIOHelper ioHelper,
ILocalizedTextService localizedTextService,
IEditorConfigurationParser editorConfigurationParser)
: this(
dataValueEditorFactory,
validators,
ioHelper,
localizedTextService,
editorConfigurationParser,
StaticServiceProvider.Instance.GetRequiredService<ITagPropertyIndexValueFactory>())
{
}
public TagsPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
ManifestValueValidatorCollection validators,
IIOHelper ioHelper,
ILocalizedTextService localizedTextService,
IEditorConfigurationParser editorConfigurationParser)
IEditorConfigurationParser editorConfigurationParser,
ITagPropertyIndexValueFactory tagPropertyIndexValueFactory)
: base(dataValueEditorFactory)
{
_validators = validators;
_ioHelper = ioHelper;
_localizedTextService = localizedTextService;
_editorConfigurationParser = editorConfigurationParser;
_tagPropertyIndexValueFactory = tagPropertyIndexValueFactory;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _tagPropertyIndexValueFactory;
protected override IDataValueEditor CreateValueEditor() =>
DataValueEditorFactory.Create<TagPropertyValueEditor>(Attribute!);

View File

@@ -1,8 +1,10 @@
using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Security;
@@ -13,9 +15,21 @@ namespace Umbraco.Cms.Web.BackOffice.Security;
public sealed class ConfigureBackOfficeIdentityOptions : IConfigureOptions<BackOfficeIdentityOptions>
{
private readonly UserPasswordConfigurationSettings _userPasswordConfiguration;
private readonly SecuritySettings _securitySettings;
public ConfigureBackOfficeIdentityOptions(IOptions<UserPasswordConfigurationSettings> userPasswordConfiguration) =>
[Obsolete("Use the constructor that accepts SecuritySettings. Will be removed in V13.")]
public ConfigureBackOfficeIdentityOptions(IOptions<UserPasswordConfigurationSettings> userPasswordConfiguration)
: this(userPasswordConfiguration, StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}
public ConfigureBackOfficeIdentityOptions(
IOptions<UserPasswordConfigurationSettings> userPasswordConfiguration,
IOptions<SecuritySettings> securitySettings)
{
_userPasswordConfiguration = userPasswordConfiguration.Value;
_securitySettings = securitySettings.Value;
}
public void Configure(BackOfficeIdentityOptions options)
{
@@ -31,8 +45,7 @@ public sealed class ConfigureBackOfficeIdentityOptions : IConfigureOptions<BackO
options.ClaimsIdentity.SecurityStampClaimType = Constants.Security.SecurityStampClaimType;
options.Lockout.AllowedForNewUsers = true;
// TODO: Implement this
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30);
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(_securitySettings.UserDefaultLockoutTimeInMinutes);
options.Password.ConfigurePasswordOptions(_userPasswordConfiguration);

View File

@@ -31,8 +31,7 @@ public sealed class ConfigureMemberIdentityOptions : IConfigureOptions<IdentityO
options.Lockout.AllowedForNewUsers = true;
// TODO: Implement this
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30);
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(_securitySettings.MemberDefaultLockoutTimeInMinutes);
options.Password.ConfigurePasswordOptions(_memberPasswordConfiguration);

View File

@@ -101,12 +101,15 @@
<div class="control-group">
<label class="control-label" for="dbType">Database</label>
<div class="controls -with-border">
<div class="input-readonly-text">
<div class="input-readonly-text" ng-if="installer.current.model.customInstallAvailable">
<strong>Provider:</strong>
{{installer.current.model.quickInstallSettings.displayName}}
<br /><strong>Name:</strong>
{{installer.current.model.quickInstallSettings.defaultDatabaseName}}
</div>
<div class="input-readonly-text" ng-if="!installer.current.model.customInstallAvailable">
A database has been pre-configured for your installation.
</div>
</div>
</div>
</div>

View File

@@ -95,7 +95,8 @@ public class DataValueEditorReuseTests
_dataValueEditorFactoryMock.Object,
new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>)),
Mock.Of<IIOHelper>(),
Mock.Of<IEditorConfigurationParser>());
Mock.Of<IEditorConfigurationParser>(),
Mock.Of<IBlockValuePropertyIndexValueFactory>());
// block list is *not* set to reuse its data value editor
var dataValueEditor1 = blockListPropertyEditor.GetValueEditor();
@@ -115,7 +116,8 @@ public class DataValueEditorReuseTests
_dataValueEditorFactoryMock.Object,
new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>)),
Mock.Of<IIOHelper>(),
Mock.Of<IEditorConfigurationParser>());
Mock.Of<IEditorConfigurationParser>(),
Mock.Of<IBlockValuePropertyIndexValueFactory>());
// no matter what, a property editor should never reuse its data value editor when created *with* configuration
var dataValueEditor1 = blockListPropertyEditor.GetValueEditor("config");

View File

@@ -34,7 +34,11 @@ public class NestedContentTests
var localizationService = Mock.Of<ILocalizationService>();
PropertyEditorCollection editors = null;
var editor = new NestedContentPropertyEditor(Mock.Of<IDataValueEditorFactory>(), Mock.Of<IIOHelper>(), Mock.Of<IEditorConfigurationParser>());
var editor = new NestedContentPropertyEditor(
Mock.Of<IDataValueEditorFactory>(),
Mock.Of<IIOHelper>(),
Mock.Of<IEditorConfigurationParser>(),
Mock.Of<INestedContentPropertyIndexValueFactory>());
editors = new PropertyEditorCollection(new DataEditorCollection(() => new DataEditor[] { editor }));
var serializer = new ConfigurationEditorJsonSerializer();