Merge pull request #11386 from matthewcare/temp-11381
Request Handler Settings for character replacement
This commit is contained in:
17
src/Umbraco.Core/Configuration/Models/CharItem.cs
Normal file
17
src/Umbraco.Core/Configuration/Models/CharItem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Umbraco.Cms.Core.Configuration.UmbracoSettings;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models
|
||||
{
|
||||
public class CharItem : IChar
|
||||
{
|
||||
/// <summary>
|
||||
/// The character to replace
|
||||
/// </summary>
|
||||
public string Char { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The replacement character
|
||||
/// </summary>
|
||||
public string Replacement { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Cms.Core.Configuration.UmbracoSettings;
|
||||
@@ -16,33 +17,34 @@ namespace Umbraco.Cms.Core.Configuration.Models
|
||||
{
|
||||
internal const bool StaticAddTrailingSlash = true;
|
||||
internal const string StaticConvertUrlsToAscii = "try";
|
||||
internal const bool StaticEnableDefaultCharReplacements = true;
|
||||
|
||||
internal static readonly CharItem[] DefaultCharCollection =
|
||||
{
|
||||
new CharItem { Char = " ", Replacement = "-" },
|
||||
new CharItem { Char = "\"", Replacement = string.Empty },
|
||||
new CharItem { Char = "'", Replacement = string.Empty },
|
||||
new CharItem { Char = "%", Replacement = string.Empty },
|
||||
new CharItem { Char = ".", Replacement = string.Empty },
|
||||
new CharItem { Char = ";", Replacement = string.Empty },
|
||||
new CharItem { Char = "/", Replacement = string.Empty },
|
||||
new CharItem { Char = "\\", Replacement = string.Empty },
|
||||
new CharItem { Char = ":", Replacement = string.Empty },
|
||||
new CharItem { Char = "#", Replacement = string.Empty },
|
||||
new CharItem { Char = "+", Replacement = "plus" },
|
||||
new CharItem { Char = "*", Replacement = "star" },
|
||||
new CharItem { Char = "&", Replacement = string.Empty },
|
||||
new CharItem { Char = "?", Replacement = string.Empty },
|
||||
new CharItem { Char = "æ", Replacement = "ae" },
|
||||
new CharItem { Char = "ä", Replacement = "ae" },
|
||||
new CharItem { Char = "ø", Replacement = "oe" },
|
||||
new CharItem { Char = "ö", Replacement = "oe" },
|
||||
new CharItem { Char = "å", Replacement = "aa" },
|
||||
new CharItem { Char = "ü", Replacement = "ue" },
|
||||
new CharItem { Char = "ß", Replacement = "ss" },
|
||||
new CharItem { Char = "|", Replacement = "-" },
|
||||
new CharItem { Char = "<", Replacement = string.Empty },
|
||||
new CharItem { Char = ">", Replacement = string.Empty }
|
||||
new () { Char = " ", Replacement = "-" },
|
||||
new () { Char = "\"", Replacement = string.Empty },
|
||||
new () { Char = "'", Replacement = string.Empty },
|
||||
new () { Char = "%", Replacement = string.Empty },
|
||||
new () { Char = ".", Replacement = string.Empty },
|
||||
new () { Char = ";", Replacement = string.Empty },
|
||||
new () { Char = "/", Replacement = string.Empty },
|
||||
new () { Char = "\\", Replacement = string.Empty },
|
||||
new () { Char = ":", Replacement = string.Empty },
|
||||
new () { Char = "#", Replacement = string.Empty },
|
||||
new () { Char = "+", Replacement = "plus" },
|
||||
new () { Char = "*", Replacement = "star" },
|
||||
new () { Char = "&", Replacement = string.Empty },
|
||||
new () { Char = "?", Replacement = string.Empty },
|
||||
new () { Char = "æ", Replacement = "ae" },
|
||||
new () { Char = "ä", Replacement = "ae" },
|
||||
new () { Char = "ø", Replacement = "oe" },
|
||||
new () { Char = "ö", Replacement = "oe" },
|
||||
new () { Char = "å", Replacement = "aa" },
|
||||
new () { Char = "ü", Replacement = "ue" },
|
||||
new () { Char = "ß", Replacement = "ss" },
|
||||
new () { Char = "|", Replacement = "-" },
|
||||
new () { Char = "<", Replacement = string.Empty },
|
||||
new () { Char = ">", Replacement = string.Empty }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -67,41 +69,21 @@ namespace Umbraco.Cms.Core.Configuration.Models
|
||||
/// </summary>
|
||||
public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try");
|
||||
|
||||
// We need to special handle ":", as this character is special in keys
|
||||
|
||||
// TODO: implement from configuration
|
||||
|
||||
//// var collection = _configuration.GetSection(Prefix + "CharCollection").GetChildren()
|
||||
//// .Select(x => new CharItem()
|
||||
//// {
|
||||
//// Char = x.GetValue<string>("Char"),
|
||||
//// Replacement = x.GetValue<string>("Replacement"),
|
||||
//// }).ToArray();
|
||||
|
||||
//// if (collection.Any() || _configuration.GetSection("Prefix").GetChildren().Any(x =>
|
||||
//// x.Key.Equals("CharCollection", StringComparison.OrdinalIgnoreCase)))
|
||||
//// {
|
||||
//// return collection;
|
||||
//// }
|
||||
|
||||
//// return DefaultCharCollection;
|
||||
/// <summary>
|
||||
/// Disable all default character replacements
|
||||
/// </summary>
|
||||
[DefaultValue(StaticEnableDefaultCharReplacements)]
|
||||
public bool EnableDefaultCharReplacements { get; set; } = StaticEnableDefaultCharReplacements;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the default character collection for replacements.
|
||||
/// Add additional character replacements, or override defaults
|
||||
/// </summary>
|
||||
/// WB-TODO
|
||||
[Obsolete("Use the GetCharReplacements extension method in the Umbraco.Extensions namespace instead. Scheduled for removal in V11")]
|
||||
public IEnumerable<IChar> CharCollection { get; set; } = DefaultCharCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a character replacement.
|
||||
/// Add additional character replacements, or override defaults
|
||||
/// </summary>
|
||||
public class CharItem : IChar
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Char { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Replacement { get; set; }
|
||||
}
|
||||
public IEnumerable<CharItem> UserDefinedCharCollection { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public class CharacterReplacementEqualityComparer : IEqualityComparer<IChar>
|
||||
{
|
||||
public bool Equals(IChar x, IChar y)
|
||||
{
|
||||
if (ReferenceEquals(x, y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (y is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x.GetType() != y.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return x.Char == y.Char && x.Replacement == y.Replacement;
|
||||
}
|
||||
|
||||
public int GetHashCode(IChar obj)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return ((obj.Char != null ? obj.Char.GetHashCode() : 0) * 397) ^ (obj.Replacement != null ? obj.Replacement.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
namespace Umbraco.Cms.Core.Configuration.UmbracoSettings
|
||||
namespace Umbraco.Cms.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public interface IChar
|
||||
{
|
||||
string Char { get; }
|
||||
|
||||
string Replacement { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Configuration.Models.Validation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.DependencyInjection
|
||||
{
|
||||
@@ -76,6 +79,8 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
.AddUmbracoOptions<LegacyPasswordMigrationSettings>()
|
||||
.AddUmbracoOptions<PackageMigrationSettings>();
|
||||
|
||||
builder.Services.Configure<RequestHandlerSettings>(options => options.MergeReplacements(builder.Config));
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Configuration.UmbracoSettings;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get concatenated user and default character replacements
|
||||
/// taking into account <see cref="RequestHandlerSettings.EnableDefaultCharReplacements"/>
|
||||
/// </summary>
|
||||
public static class RequestHandlerSettingsExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Get concatenated user and default character replacements
|
||||
/// taking into account <see cref="RequestHandlerSettings.EnableDefaultCharReplacements"/>
|
||||
/// </summary>
|
||||
public static IEnumerable<CharItem> GetCharReplacements(this RequestHandlerSettings requestHandlerSettings)
|
||||
{
|
||||
if (requestHandlerSettings.EnableDefaultCharReplacements is false)
|
||||
{
|
||||
return requestHandlerSettings.UserDefinedCharCollection ?? Enumerable.Empty<CharItem>();
|
||||
}
|
||||
|
||||
if (requestHandlerSettings.UserDefinedCharCollection == null || requestHandlerSettings.UserDefinedCharCollection.Any() is false)
|
||||
{
|
||||
return RequestHandlerSettings.DefaultCharCollection;
|
||||
}
|
||||
|
||||
return MergeUnique(requestHandlerSettings.UserDefinedCharCollection, RequestHandlerSettings.DefaultCharCollection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges CharCollection and UserDefinedCharCollection, prioritizing UserDefinedCharCollection
|
||||
/// </summary>
|
||||
internal static void MergeReplacements(this RequestHandlerSettings requestHandlerSettings, IConfiguration configuration)
|
||||
{
|
||||
string sectionKey = $"{Constants.Configuration.ConfigRequestHandler}:";
|
||||
|
||||
IEnumerable<CharItem> charCollection = GetReplacements(
|
||||
configuration,
|
||||
$"{sectionKey}{nameof(RequestHandlerSettings.CharCollection)}");
|
||||
|
||||
IEnumerable<CharItem> userDefinedCharCollection = GetReplacements(
|
||||
configuration,
|
||||
$"{sectionKey}{nameof(requestHandlerSettings.UserDefinedCharCollection)}");
|
||||
|
||||
IEnumerable<CharItem> mergedCollection = MergeUnique(userDefinedCharCollection, charCollection);
|
||||
|
||||
requestHandlerSettings.UserDefinedCharCollection = mergedCollection;
|
||||
}
|
||||
|
||||
private static IEnumerable<CharItem> GetReplacements(IConfiguration configuration, string key)
|
||||
{
|
||||
var replacements = new List<CharItem>();
|
||||
IEnumerable<IConfigurationSection> config = configuration.GetSection(key).GetChildren();
|
||||
|
||||
foreach (IConfigurationSection section in config)
|
||||
{
|
||||
var @char = section.GetValue<string>(nameof(CharItem.Char));
|
||||
var replacement = section.GetValue<string>(nameof(CharItem.Replacement));
|
||||
replacements.Add(new CharItem { Char = @char, Replacement = replacement });
|
||||
}
|
||||
|
||||
return replacements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges two IEnumerable of CharItem without any duplicates, items in priorityReplacements will override those in alternativeReplacements
|
||||
/// </summary>
|
||||
private static IEnumerable<CharItem> MergeUnique(
|
||||
IEnumerable<CharItem> priorityReplacements,
|
||||
IEnumerable<CharItem> alternativeReplacements)
|
||||
{
|
||||
var priorityReplacementsList = priorityReplacements.ToList();
|
||||
var alternativeReplacementsList = alternativeReplacements.ToList();
|
||||
|
||||
foreach (CharItem alternativeReplacement in alternativeReplacementsList)
|
||||
{
|
||||
foreach (CharItem priorityReplacement in priorityReplacementsList)
|
||||
{
|
||||
if (priorityReplacement.Char == alternativeReplacement.Char)
|
||||
{
|
||||
alternativeReplacement.Replacement = priorityReplacement.Replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return priorityReplacementsList.Union<CharItem>(
|
||||
alternativeReplacementsList,
|
||||
new CharacterReplacementEqualityComparer());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Strings
|
||||
@@ -60,7 +61,9 @@ namespace Umbraco.Cms.Core.Strings
|
||||
/// <returns>The short string helper.</returns>
|
||||
public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings requestHandlerSettings)
|
||||
{
|
||||
UrlReplaceCharacters = requestHandlerSettings.CharCollection
|
||||
IEnumerable<IChar> charCollection = requestHandlerSettings.GetCharReplacements();
|
||||
|
||||
UrlReplaceCharacters = charCollection
|
||||
.Where(x => string.IsNullOrEmpty(x.Char) == false)
|
||||
.ToDictionary(x => x.Char, x => x.Replacement);
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models
|
||||
{
|
||||
[TestFixture]
|
||||
public class RequestHandlerSettingsTests
|
||||
{
|
||||
[Test]
|
||||
public void Given_CharCollection_With_DefaultEnabled_MergesCollection()
|
||||
{
|
||||
var userCollection = new CharItem[]
|
||||
{
|
||||
new () { Char = "test", Replacement = "replace" },
|
||||
new () { Char = "test2", Replacement = "replace2" }
|
||||
};
|
||||
|
||||
|
||||
var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection };
|
||||
var actual = settings.GetCharReplacements().ToList();
|
||||
|
||||
var expectedCollection = RequestHandlerSettings.DefaultCharCollection.ToList();
|
||||
expectedCollection.AddRange(userCollection);
|
||||
|
||||
Assert.AreEqual(expectedCollection.Count, actual.Count);
|
||||
Assert.That(actual, Is.EquivalentTo(expectedCollection));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Given_CharCollection_With_DefaultDisabled_ReturnsUserCollection()
|
||||
{
|
||||
var userCollection = new CharItem[]
|
||||
{
|
||||
new () { Char = "test", Replacement = "replace" },
|
||||
new () { Char = "test2", Replacement = "replace2" }
|
||||
};
|
||||
|
||||
var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection, EnableDefaultCharReplacements = false };
|
||||
var actual = settings.GetCharReplacements().ToList();
|
||||
|
||||
Assert.AreEqual(userCollection.Length, actual.Count);
|
||||
Assert.That(actual, Is.EquivalentTo(userCollection));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Given_CharCollection_That_OverridesDefaultValues_ReturnsReplacements()
|
||||
{
|
||||
var userCollection = new CharItem[]
|
||||
{
|
||||
new () { Char = "%", Replacement = "percent" },
|
||||
new () { Char = ".", Replacement = "dot" }
|
||||
};
|
||||
|
||||
var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection };
|
||||
var actual = settings.GetCharReplacements().ToList();
|
||||
|
||||
Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length, actual.Count);
|
||||
|
||||
Assert.That(actual, Has.Exactly(1).Matches<CharItem>(x => x.Char == "%" && x.Replacement == "percent"));
|
||||
Assert.That(actual, Has.Exactly(1).Matches<CharItem>(x => x.Char == "." && x.Replacement == "dot"));
|
||||
Assert.That(actual, Has.Exactly(0).Matches<CharItem>(x => x.Char == "%" && x.Replacement == string.Empty));
|
||||
Assert.That(actual, Has.Exactly(0).Matches<CharItem>(x => x.Char == "." && x.Replacement == string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Given_CharCollection_That_OverridesDefaultValues_And_ContainsNew_ReturnsMergedWithReplacements()
|
||||
{
|
||||
var userCollection = new CharItem[]
|
||||
{
|
||||
new () { Char = "%", Replacement = "percent" },
|
||||
new () { Char = ".", Replacement = "dot" },
|
||||
new () { Char = "new", Replacement = "new" }
|
||||
};
|
||||
|
||||
var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection };
|
||||
var actual = settings.GetCharReplacements().ToList();
|
||||
|
||||
// Add 1 to the length, because we're expecting to only add one new one
|
||||
Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length + 1, actual.Count);
|
||||
|
||||
Assert.That(actual, Has.Exactly(1).Matches<CharItem>(x => x.Char == "%" && x.Replacement == "percent"));
|
||||
Assert.That(actual, Has.Exactly(1).Matches<CharItem>(x => x.Char == "." && x.Replacement == "dot"));
|
||||
Assert.That(actual, Has.Exactly(1).Matches<CharItem>(x => x.Char == "new" && x.Replacement == "new"));
|
||||
Assert.That(actual, Has.Exactly(0).Matches<CharItem>(x => x.Char == "%" && x.Replacement == string.Empty));
|
||||
Assert.That(actual, Has.Exactly(0).Matches<CharItem>(x => x.Char == "." && x.Replacement == string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -19,7 +20,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings()
|
||||
{
|
||||
CharCollection = Enumerable.Empty<IChar>(),
|
||||
CharCollection = Array.Empty<CharItem>(),
|
||||
EnableDefaultCharReplacements = false,
|
||||
ConvertUrlsToAscii = "false"
|
||||
};
|
||||
|
||||
@@ -45,7 +47,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings()
|
||||
{
|
||||
CharCollection = Enumerable.Empty<IChar>(),
|
||||
CharCollection = Array.Empty<CharItem>(),
|
||||
EnableDefaultCharReplacements = false,
|
||||
ConvertUrlsToAscii = "false"
|
||||
};
|
||||
|
||||
@@ -339,7 +342,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings()
|
||||
{
|
||||
CharCollection = Enumerable.Empty<IChar>(),
|
||||
CharCollection = Array.Empty<CharItem>(),
|
||||
EnableDefaultCharReplacements = false,
|
||||
ConvertUrlsToAscii = "false"
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user