Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,13 @@ public void Assign(int contentId, int propertyTypeId, IEnumerable<ITag> tags, bo
var group = SqlSyntax.GetQuotedColumnName("group");

// insert tags
// - Note we are checking in the subquery for the existence of the tag, so we don't insert duplicates, using a case-insensitive comparison (the
// LOWER keyword is consistent across SQLite and SQLServer). This ensures consistent behavior across databases as by default, SQLServer will
// perform a case-insensitive comparison, while SQLite will not.
var sql1 = $@"INSERT INTO cmsTags (tag, {group}, languageId)
SELECT tagSet.tag, tagSet.{group}, tagSet.languageId
FROM {tagSetSql}
LEFT OUTER JOIN cmsTags ON (tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1))
LEFT OUTER JOIN cmsTags ON (LOWER(tagSet.tag) = LOWER(cmsTags.tag) AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1))
Comment thread
AndyButland marked this conversation as resolved.
Outdated
Comment thread
Migaroez marked this conversation as resolved.
Outdated
WHERE cmsTags.id IS NULL";

Database.Execute(sql1);
Expand All @@ -142,7 +145,7 @@ LEFT OUTER JOIN cmsTags ON (tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTag
FROM (
SELECT t.Id
FROM {tagSetSql}
INNER JOIN cmsTags as t ON (tagSet.tag = t.tag AND tagSet.{group} = t.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(t.languageId, -1))
INNER JOIN cmsTags as t ON (LOWER(tagSet.tag) = LOWER(t.tag) AND tagSet.{group} = t.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(t.languageId, -1))
) AS tagSet2
LEFT OUTER JOIN cmsTagRelationship r ON (tagSet2.id = r.tagId AND r.nodeId = {contentId} AND r.propertyTypeID = {propertyTypeId})
WHERE r.tagId IS NULL";
Expand Down Expand Up @@ -245,13 +248,13 @@ private class TagComparer : IEqualityComparer<ITag>
{
public bool Equals(ITag? x, ITag? y) =>
ReferenceEquals(x, y) // takes care of both being null
|| (x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId);
|| (x != null && y != null && x.Text.ToLowerInvariant() == y.Text.ToLowerInvariant() && x.Group == y.Group && x.LanguageId == y.LanguageId);

public int GetHashCode(ITag obj)
{
unchecked
{
var h = obj.Text.GetHashCode();
var h = obj.Text.ToLowerInvariant().GetHashCode();
Comment thread
AndyButland marked this conversation as resolved.
Outdated
h = (h * 397) ^ obj.Group.GetHashCode();
h = (h * 397) ^ (obj.LanguageId?.GetHashCode() ?? 0);
return h;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using System.Linq;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
Expand Down Expand Up @@ -1047,6 +1046,51 @@ public void Can_Get_Tagged_Entities_For_Tag()
}
}

[Test]
public void Can_Create_Tag_Relations_With_Mixed_Casing()
{
var provider = ScopeProvider;
using (var scope = ScopeProvider.CreateScope())
{
var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);

var contentType =
ContentTypeBuilder.CreateSimpleContentType("test", "Test", defaultTemplateId: template.Id);
ContentTypeRepository.Save(contentType);

var content1 = ContentBuilder.CreateSimpleContent(contentType);
var content2 = ContentBuilder.CreateSimpleContent(contentType);
DocumentRepository.Save(content1);
DocumentRepository.Save(content2);

var repository = CreateRepository(provider);
// Note two tags are applied, but they differ only in case.
Tag[] tags1 = { new() { Text = "tag1", Group = "test" }, new() { Text = "Tag1", Group = "test" } };
repository.Assign(
content1.Id,
contentType.PropertyTypes.First().Id,
tags1,
false);

// Note the casing is different from tags1, but both should be considered equivalent.
Tag[] tags2 = { new() { Text = "TAG1", Group = "test" } };
repository.Assign(
content2.Id,
contentType.PropertyTypes.First().Id,
tags2,
false);

// Only one tag should have been saved.
var tagCount = scope.Database.ExecuteScalar<int>(
"SELECT COUNT(*) FROM cmsTags WHERE [group] = 'test'");
Assert.AreEqual(1, tagCount);

// Both content items should be found as tagged by the tag, even though one was assigned with the tag differing in case.
Assert.AreEqual(2, repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, "tag1").Count());
}
}

private TagRepository CreateRepository(IScopeProvider provider) =>
new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<TagRepository>());
}