diff --git a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs
index 8fe15645e1..0a290e7492 100644
--- a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs
@@ -40,7 +40,7 @@ public class TextStringValueConverter : PropertyValueConverterBase, IDeliveryApi
var sourceString = source.ToString();
// ensures string is parsed for {localLink} and URLs are resolved correctly
- sourceString = _linkParser.EnsureInternalLinks(sourceString!, preview);
+ sourceString = _linkParser.EnsureInternalLinks(sourceString!);
sourceString = _urlParser.EnsureUrls(sourceString);
return sourceString;
diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs
index 4714ebcd2e..73aec2e74d 100644
--- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs
+++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs
@@ -1,5 +1,6 @@
using System.Globalization;
using System.Text.RegularExpressions;
+using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
namespace Umbraco.Cms.Core.Templates;
@@ -45,17 +46,18 @@ public sealed class HtmlLocalLinkParser
///
/// Parses the string looking for the {localLink} syntax and updates them to their correct links.
///
- ///
- ///
- ///
+ [Obsolete("This method overload is no longer used in Umbraco and delegates to the overload without the preview parameter. Scheduled for removal in Umbraco 18.")]
public string EnsureInternalLinks(string text, bool preview) => EnsureInternalLinks(text);
///
/// Parses the string looking for the {localLink} syntax and updates them to their correct links.
///
- ///
- ///
- public string EnsureInternalLinks(string text)
+ public string EnsureInternalLinks(string text) => EnsureInternalLinks(text, UrlMode.Default);
+
+ ///
+ /// Parses the string looking for the {localLink} syntax and updates them to their correct links.
+ ///
+ public string EnsureInternalLinks(string text, UrlMode urlMode)
{
foreach (LocalLinkTag tagData in FindLocalLinkIds(text))
{
@@ -63,8 +65,8 @@ public sealed class HtmlLocalLinkParser
{
var newLink = tagData.Udi?.EntityType switch
{
- Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(tagData.Udi.Guid),
- Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(tagData.Udi.Guid),
+ Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(tagData.Udi.Guid, urlMode),
+ Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(tagData.Udi.Guid, urlMode),
_ => string.Empty,
};
@@ -73,7 +75,7 @@ public sealed class HtmlLocalLinkParser
}
else if (tagData.IntId.HasValue)
{
- var newLink = _publishedUrlProvider.GetUrl(tagData.IntId.Value);
+ var newLink = _publishedUrlProvider.GetUrl(tagData.IntId.Value, urlMode);
text = text.Replace(tagData.TagHref, newLink);
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
index 88f9540a4f..882b678a2e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
@@ -671,6 +671,15 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
protected override void PersistDeletedItem(IUser entity)
{
+ // Clear user group caches for any user groups associated with the deleted user.
+ // We need to do this because the count of the number of users in the user group is cached
+ // along with the user group, and if we've made changes to the user groups assigned to the user,
+ // the count for the groups need to be refreshed.
+ foreach (IReadOnlyUserGroup group in entity.Groups)
+ {
+ ClearRepositoryCacheForUserGroup(group.Id);
+ }
+
IEnumerable deletes = GetDeleteClauses();
foreach (var delete in deletes)
{
@@ -713,8 +722,8 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
if (entity.IsPropertyDirty("Groups"))
{
// lookup all assigned
- List? assigned = entity.Groups == null || entity.Groups.Any() == false
- ? new List()
+ List? assigned = entity.Groups.Any() is false
+ ? []
: Database.Fetch(
"SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)",
new { aliases = entity.Groups.Select(x => x.Alias) });
@@ -724,6 +733,15 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
var dto = new User2UserGroupDto { UserGroupId = groupDto.Id, UserId = entity.Id };
Database.Insert(dto);
}
+
+ // Clear user group caches for the user groups associated with the new user.
+ // We need to do this because the count of the number of users in the user group is cached
+ // along with the user group, and if we've made changes to the user groups assigned to the user,
+ // the count for the groups need to be refreshed.
+ foreach (IReadOnlyUserGroup group in entity.Groups)
+ {
+ ClearRepositoryCacheForUserGroup(group.Id);
+ }
}
entity.ResetDirtyProperties();
@@ -836,27 +854,66 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
if (entity.IsPropertyDirty("Groups"))
{
- //lookup all assigned
- List? assigned = entity.Groups == null || entity.Groups.Any() == false
- ? new List()
- : Database.Fetch(
- "SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)",
- new { aliases = entity.Groups.Select(x => x.Alias) });
+ // Get all user groups Ids currently assigned to the user.
+ var existingUserGroupIds = Database.Fetch(
+ "WHERE UserId = @UserId",
+ new { UserId = entity.Id })
+ .Select(x => x.UserGroupId)
+ .ToList();
- //first delete all
- // TODO: We could do this a nicer way instead of "Nuke and Pave"
- Database.Delete("WHERE UserId = @UserId", new { UserId = entity.Id });
+ // Get the user groups Ids that need to be removed and added.
+ var userGroupsIdsToRemove = existingUserGroupIds
+ .Except(entity.Groups.Select(x => x.Id))
+ .ToList();
+ var userGroupIdsToAdd = entity.Groups
+ .Select(x => x.Id)
+ .Except(existingUserGroupIds)
+ .ToList();
- foreach (UserGroupDto? groupDto in assigned)
+ // Remove user groups that are no longer assigned to the user.
+ if (userGroupsIdsToRemove.Count > 0)
{
- var dto = new User2UserGroupDto { UserGroupId = groupDto.Id, UserId = entity.Id };
- Database.Insert(dto);
+ Database.Delete(
+ "WHERE UserId = @UserId AND UserGroupId IN (@userGroupIds)",
+ new { UserId = entity.Id, userGroupIds = userGroupsIdsToRemove });
+ }
+
+ // Add user groups that are newly assigned to the user.
+ if (userGroupIdsToAdd.Count > 0)
+ {
+ IEnumerable user2UserGroupDtos = userGroupIdsToAdd
+ .Select(userGroupId => new User2UserGroupDto
+ {
+ UserGroupId = userGroupId,
+ UserId = entity.Id,
+ });
+ Database.InsertBulk(user2UserGroupDtos);
+ }
+
+ // Clear user group caches for any user group that have been removed or added.
+ // We need to do this because the count of the number of users in the user group is cached
+ // along with the user group, and if we've made changes to the user groups assigned to the user,
+ // the count for the groups need to be refreshed.
+ var userGroupIdsToRefresh = userGroupsIdsToRemove
+ .Union(userGroupIdsToAdd)
+ .ToList();
+ foreach (int userGroupIdToRefresh in userGroupIdsToRefresh)
+ {
+ ClearRepositoryCacheForUserGroup(userGroupIdToRefresh);
}
}
entity.ResetDirtyProperties();
}
+ private void ClearRepositoryCacheForUserGroup(int id)
+ {
+ IAppPolicyCache userGroupCache = AppCaches.IsolatedCaches.GetOrCreate();
+
+ string cacheKey = RepositoryCacheKeys.GetKey(id);
+ userGroupCache.Clear(cacheKey);
+ }
+
private void AddingOrUpdateStartNodes(IEntity entity, IEnumerable current,
UserStartNodeDto.StartNodeTypeValue startNodeType, int[]? entityStartIds)
{
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs
index 05c6a8a4f1..ff0962a827 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs
@@ -41,7 +41,7 @@ public class MarkdownEditorValueConverter : PropertyValueConverterBase, IDeliver
var sourceString = source.ToString()!;
// ensures string is parsed for {localLink} and URLs are resolved correctly
- sourceString = _localLinkParser.EnsureInternalLinks(sourceString, preview);
+ sourceString = _localLinkParser.EnsureInternalLinks(sourceString);
sourceString = _urlParser.EnsureUrls(sourceString);
return sourceString;
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs
index b2c47fc3cb..d39d13e243 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs
@@ -135,7 +135,7 @@ public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDe
var sourceString = intermediateValue.Markup;
// ensures string is parsed for {localLink} and URLs and media are resolved correctly
- sourceString = _linkParser.EnsureInternalLinks(sourceString, preview);
+ sourceString = _linkParser.EnsureInternalLinks(sourceString);
sourceString = _urlParser.EnsureUrls(sourceString);
sourceString = _imageSourceParser.EnsureImageSources(sourceString);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts
index 2e898bf7e8..0e6132cefc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts
@@ -266,6 +266,15 @@ export class UmbInputTiptapElement extends UmbFormControlMixin { Constants.Security.AdminGroupKey };
+ var updateResult = await userService.UpdateAsync(Constants.Security.SuperUserKey, updateModel);
+ Assert.IsTrue(updateResult.Success);
+ }
+
+ var adminUser = await userService.GetAsync(Constants.Security.SuperUserKey);
+ var adminUserUpdateModel = await MapUserToUpdateModel(adminUser);
+ adminUserUpdateModel.Email = "admin@test.com";
+ adminUserUpdateModel.UserGroupKeys = new HashSet { Constants.Security.EditorGroupKey };
+ var adminUserUpdateResult = await userService.UpdateAsync(Constants.Security.SuperUserKey, adminUserUpdateModel);
+
+ if (expectSuccess)
+ {
+ Assert.IsTrue(adminUserUpdateResult.Success);
+ }
+ else
+ {
+ Assert.IsFalse(adminUserUpdateResult.Success);
+ Assert.AreEqual(UserOperationStatus.AdminUserGroupMustNotBeEmpty, adminUserUpdateResult.Status);
+ }
+ }
}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs
index d5ef54ece6..6c8e737493 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs
@@ -1019,6 +1019,42 @@ internal sealed class UserServiceTests : UmbracoIntegrationTest
}
}
+ [Test]
+ public async Task Can_Assign_And_Get_Groups_For_User()
+ {
+ // Arrange
+ var (user, userGroup1) = await CreateTestUserAndGroup();
+ var userGroup2 = await CreateTestUserGroup("testGroup2", "Test Group 2");
+
+ // Act & Assert
+ user = UserService.GetByUsername(user.Username);
+
+ Assert.IsNotNull(user);
+ Assert.AreEqual(1, user.Groups.Count());
+ Assert.AreEqual(userGroup1.Alias, user.Groups.First().Alias);
+
+ // - add second group
+ user.AddGroup(userGroup2);
+ UserService.Save(user);
+ user = UserService.GetByUsername(user.Username);
+ Assert.AreEqual(2, user.Groups.Count());
+
+ // - remove first group
+ user.RemoveGroup(userGroup1.Alias);
+ UserService.Save(user);
+ user = UserService.GetByUsername(user.Username);
+ Assert.AreEqual(1, user.Groups.Count());
+ Assert.AreEqual(userGroup2.Alias, user.Groups.First().Alias);
+
+ // - remove second group and add first
+ user.RemoveGroup(userGroup2.Alias);
+ user.AddGroup(userGroup1.ToReadOnlyGroup());
+ UserService.Save(user);
+ user = UserService.GetByUsername(user.Username);
+ Assert.AreEqual(1, user.Groups.Count());
+ Assert.AreEqual(userGroup1.Alias, user.Groups.First().Alias);
+ }
+
[TestCase(UserKind.Default, UserClientCredentialsOperationStatus.InvalidUser)]
[TestCase(UserKind.Api, UserClientCredentialsOperationStatus.Success)]
public async Task Can_Assign_ClientId_To_Api_User(UserKind userKind, UserClientCredentialsOperationStatus expectedResult)
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs
index d1e5e0f494..0aa00b48d6 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs
@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Templates;
+using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Tests.Common;
using Umbraco.Cms.Tests.UnitTests.TestHelpers.Objects;
@@ -216,18 +217,204 @@ public class HtmlLocalLinkParserTests
var umbracoContextAccessor = new TestUmbracoContextAccessor();
+ var umbracoContextFactory = TestUmbracoContextFactory.Create(
+ umbracoContextAccessor: umbracoContextAccessor);
+
+ using (var reference = umbracoContextFactory.EnsureUmbracoContext())
+ {
+ var contentCache = Mock.Get(reference.UmbracoContext.Content);
+ contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object);
+ contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object);
+
+ var mediaCache = Mock.Get(reference.UmbracoContext.Media);
+ mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object);
+ mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object);
+
+ var publishedUrlProvider = CreatePublishedUrlProvider(
+ contentUrlProvider,
+ mediaUrlProvider,
+ umbracoContextAccessor);
+
+ var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
+
+ var output = linkParser.EnsureInternalLinks(input);
+
+ Assert.AreEqual(result, output);
+ }
+ }
+
+ [Test]
+ public void ParseLocalLinks_WithUrlMode_RespectsUrlMode()
+ {
+ // Arrange
+ var input = "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world";
+
+ // Setup content URL provider that returns different URLs based on UrlMode
+ var contentUrlProvider = new Mock();
+ contentUrlProvider
+ .Setup(x => x.GetUrl(
+ It.IsAny(),
+ UrlMode.Relative,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("/relative-url"));
+ contentUrlProvider
+ .Setup(x => x.GetUrl(
+ It.IsAny(),
+ UrlMode.Absolute,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("http://example.com/absolute-url"));
+
+ var contentType = new PublishedContentType(
+ Guid.NewGuid(),
+ 666,
+ "alias",
+ PublishedItemType.Content,
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ ContentVariation.Nothing);
+ var publishedContent = new Mock();
+ publishedContent.Setup(x => x.Id).Returns(1234);
+ publishedContent.Setup(x => x.ContentType).Returns(contentType);
+
+ var umbracoContextAccessor = new TestUmbracoContextAccessor();
var umbracoContextFactory = TestUmbracoContextFactory.Create(
umbracoContextAccessor: umbracoContextAccessor);
var webRoutingSettings = new WebRoutingSettings();
- var navigationQueryService = new Mock();
- // Guid? parentKey = null;
- // navigationQueryService.Setup(x => x.TryGetParentKey(It.IsAny(), out parentKey)).Returns(true);
- IEnumerable ancestorKeys = [];
- navigationQueryService.Setup(x => x.TryGetAncestorsKeys(It.IsAny(), out ancestorKeys)).Returns(true);
+ var publishedUrlProvider = CreatePublishedUrlProvider(
+ contentUrlProvider,
+ new Mock(),
+ umbracoContextAccessor);
- var publishedContentStatusFilteringService = new Mock();
+ using (var reference = umbracoContextFactory.EnsureUmbracoContext())
+ {
+ var contentCache = Mock.Get(reference.UmbracoContext.Content);
+ contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object);
+
+ var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
+
+ // Act
+ var relativeOutput = linkParser.EnsureInternalLinks(input, UrlMode.Relative);
+ var absoluteOutput = linkParser.EnsureInternalLinks(input, UrlMode.Absolute);
+
+ // Assert
+ Assert.AreEqual("hello href=\"/relative-url\" world", relativeOutput);
+ Assert.AreEqual("hello href=\"http://example.com/absolute-url\" world", absoluteOutput);
+ }
+ }
+
+ [TestCase(UrlMode.Default, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
+ [TestCase(UrlMode.Relative, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
+ [TestCase(UrlMode.Absolute, "hello href=\"{localLink:1234}\" world ", "hello href=\"https://example.com/absolute-url\" world ")]
+ [TestCase(UrlMode.Auto, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
+ [TestCase(UrlMode.Default, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
+ [TestCase(UrlMode.Relative, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
+ [TestCase(UrlMode.Absolute, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"https://example.com/absolute-url\" world ")]
+ [TestCase(UrlMode.Auto, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
+ [TestCase(UrlMode.Default, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
+ [TestCase(UrlMode.Relative, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
+ [TestCase(UrlMode.Absolute, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"https://example.com/media/absolute/image.jpg\" world ")]
+ [TestCase(UrlMode.Auto, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
+ public void ParseLocalLinks_WithVariousUrlModes_ReturnsCorrectUrls(UrlMode urlMode, string input, string expectedResult)
+ {
+ // Setup content URL provider that returns different URLs based on UrlMode
+ var contentUrlProvider = new Mock();
+ contentUrlProvider
+ .Setup(x => x.GetUrl(
+ It.IsAny(),
+ UrlMode.Default,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("/relative-url"));
+ contentUrlProvider
+ .Setup(x => x.GetUrl(
+ It.IsAny(),
+ UrlMode.Relative,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("/relative-url"));
+ contentUrlProvider
+ .Setup(x => x.GetUrl(
+ It.IsAny(),
+ UrlMode.Absolute,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("https://example.com/absolute-url"));
+ contentUrlProvider
+ .Setup(x => x.GetUrl(
+ It.IsAny(),
+ UrlMode.Auto,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("/relative-url"));
+
+ var contentType = new PublishedContentType(
+ Guid.NewGuid(),
+ 666,
+ "alias",
+ PublishedItemType.Content,
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ ContentVariation.Nothing);
+ var publishedContent = new Mock();
+ publishedContent.Setup(x => x.Id).Returns(1234);
+ publishedContent.Setup(x => x.ContentType).Returns(contentType);
+
+ // Setup media URL provider that returns different URLs based on UrlMode
+ var mediaUrlProvider = new Mock();
+ mediaUrlProvider.Setup(x => x.GetMediaUrl(
+ It.IsAny(),
+ It.IsAny(),
+ UrlMode.Default,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("/media/relative/image.jpg"));
+ mediaUrlProvider.Setup(x => x.GetMediaUrl(
+ It.IsAny(),
+ It.IsAny(),
+ UrlMode.Relative,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("/media/relative/image.jpg"));
+ mediaUrlProvider.Setup(x => x.GetMediaUrl(
+ It.IsAny(),
+ It.IsAny(),
+ UrlMode.Absolute,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("https://example.com/media/absolute/image.jpg"));
+ mediaUrlProvider.Setup(x => x.GetMediaUrl(
+ It.IsAny(),
+ It.IsAny(),
+ UrlMode.Auto,
+ It.IsAny(),
+ It.IsAny()))
+ .Returns(UrlInfo.Url("/media/relative/image.jpg"));
+
+ var mediaType = new PublishedContentType(
+ Guid.NewGuid(),
+ 777,
+ "image",
+ PublishedItemType.Media,
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ ContentVariation.Nothing);
+ var media = new Mock();
+ media.Setup(x => x.ContentType).Returns(mediaType);
+
+ var umbracoContextAccessor = new TestUmbracoContextAccessor();
+ var umbracoContextFactory = TestUmbracoContextFactory.Create(
+ umbracoContextAccessor: umbracoContextAccessor);
+
+ var webRoutingSettings = new WebRoutingSettings();
+
+ var publishedUrlProvider = CreatePublishedUrlProvider(
+ contentUrlProvider,
+ mediaUrlProvider,
+ umbracoContextAccessor);
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
{
@@ -239,25 +426,35 @@ public class HtmlLocalLinkParserTests
mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object);
mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object);
- var publishStatusQueryService = new Mock();
- publishStatusQueryService
- .Setup(x => x.IsDocumentPublished(It.IsAny(), It.IsAny()))
- .Returns(true);
-
- var publishedUrlProvider = new UrlProvider(
- umbracoContextAccessor,
- Options.Create(webRoutingSettings),
- new UrlProviderCollection(() => new[] { contentUrlProvider.Object }),
- new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
- Mock.Of(),
- navigationQueryService.Object,
- publishedContentStatusFilteringService.Object);
-
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
- var output = linkParser.EnsureInternalLinks(input);
+ var output = linkParser.EnsureInternalLinks(input, urlMode);
- Assert.AreEqual(result, output);
+ Assert.AreEqual(expectedResult, output);
}
}
+
+ private static UrlProvider CreatePublishedUrlProvider(
+ Mock contentUrlProvider,
+ Mock mediaUrlProvider,
+ TestUmbracoContextAccessor umbracoContextAccessor)
+ {
+ var navigationQueryService = new Mock();
+ IEnumerable ancestorKeys = [];
+ navigationQueryService.Setup(x => x.TryGetAncestorsKeys(It.IsAny(), out ancestorKeys)).Returns(true);
+
+ var publishStatusQueryService = new Mock();
+ publishStatusQueryService
+ .Setup(x => x.IsDocumentPublished(It.IsAny(), It.IsAny()))
+ .Returns(true);
+
+ return new UrlProvider(
+ umbracoContextAccessor,
+ Options.Create(new WebRoutingSettings()),
+ new UrlProviderCollection(() => new[] { contentUrlProvider.Object }),
+ new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
+ Mock.Of(),
+ navigationQueryService.Object,
+ new Mock().Object);
+ }
}