diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs
index 44e5968a9f..1cbb4a6ea1 100644
--- a/src/Umbraco.Core/ObjectExtensions.cs
+++ b/src/Umbraco.Core/ObjectExtensions.cs
@@ -595,7 +595,6 @@ namespace Umbraco.Core
return null;
}
-
///
/// Attempts to serialize the value to an XmlString using ToXmlString
///
@@ -788,5 +787,20 @@ namespace Umbraco.Core
return BoolConvertCache[type] = false;
}
+
+ ///
+ /// Indicates whether two nullable values are equal, substituting a fallback value for nulls.
+ ///
+ /// The nullable type.
+ /// The value to compare.
+ /// The value to compare to.
+ /// The value to use when any value is null.
+ /// Do not use outside of Sql expressions.
+ // see usage in ExpressionVisitorBase
+ public static bool NEquals(this T? value, T? other, T fallbackValue)
+ where T : struct
+ {
+ return (value ?? fallbackValue).Equals(other ?? fallbackValue);
+ }
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/TagRelationshipDto.cs b/src/Umbraco.Core/Persistence/Dtos/TagRelationshipDto.cs
index 4a07b16a07..cbe4cf0cd4 100644
--- a/src/Umbraco.Core/Persistence/Dtos/TagRelationshipDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/TagRelationshipDto.cs
@@ -3,11 +3,13 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Persistence.Dtos
{
- [TableName(Constants.DatabaseSchema.Tables.TagRelationship)]
+ [TableName(TableName)]
[PrimaryKey("nodeId", AutoIncrement = false)]
[ExplicitColumns]
internal class TagRelationshipDto
{
+ public const string TableName = Constants.DatabaseSchema.Tables.TagRelationship;
+
[Column("nodeId")]
[PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsTagRelationship", OnColumns = "nodeId, propertyTypeId, tagId")]
[ForeignKey(typeof(ContentDto), Name = "FK_cmsTagRelationship_cmsContent", Column = "nodeId")]
diff --git a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs
index d313d27bbc..493bbcacc7 100644
--- a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs
+++ b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs
@@ -653,6 +653,23 @@ namespace Umbraco.Core.Persistence.Querying
else
throw new NotSupportedException("Expression is not a proper lambda.");
+ // c# 'x == null' becomes sql 'x IS NULL' which is fine
+ // c# 'x == y' becomes sql 'x = @0' which is fine - unless they are nullable types,
+ // because sql 'x = NULL' is always false and the 'IS NULL' syntax is required,
+ // so for comparing nullable types, we use x.NEquals(y, fb) where fb is a fallback
+ // value which will be used when values are null - turning the comparison into
+ // sql 'COALESCE(x,fb) = COALESCE(y,fb)' - of course, fb must be a value outside
+ // of x and y range - and if that is not possible, then a manual comparison need
+ // to be written
+ //TODO support NEquals with 0 parameters, using the full syntax below
+ case "NEquals":
+ var compareTo = Visit(m.Arguments[1]);
+ var fallback = Visit(m.Arguments[2]);
+ // that would work without a fallback value but is more cumbersome
+ //return Visited ? string.Empty : $"((({compareTo} is null) AND ({visitedMethodObject} is null)) OR (({compareTo} is not null) AND ({visitedMethodObject} = {compareTo})))";
+ // use a fallback value
+ return Visited ? string.Empty : $"(COALESCE({visitedMethodObject},{fallback}) = COALESCE({compareTo},{fallback}))";
+
default:
throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 3184c69dfe..7e720ba08a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -190,8 +190,8 @@ AND umbracoNode.nodeObjectType = @objectType",
SortOrder = allowedContentType.SortOrder
});
}
-
-
+
+
//Insert Tabs
foreach (var propertyGroup in entity.PropertyGroups)
{
@@ -620,7 +620,7 @@ AND umbracoNode.id <> @id",
var sqlDelete = Sql()
.Delete()
.WhereIn((System.Linq.Expressions.Expression>)(x => x.ContentKey), sqlSelect);
-
+
Database.Execute(sqlDelete);
}
@@ -663,9 +663,11 @@ AND umbracoNode.id <> @id",
{
case ContentVariation.Culture:
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
+ CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
break;
case ContentVariation.Nothing:
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
+ CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
@@ -690,7 +692,7 @@ AND umbracoNode.id <> @id",
//first clear out any existing names that might already exists under the default lang
//there's 2x tables to update
- //clear out the versionCultureVariation table
+ //clear out the versionCultureVariation table
var sqlSelect = Sql().Select(x => x.Id)
.From()
.InnerJoin().On(x => x.Id, x => x.VersionId)
@@ -757,6 +759,129 @@ AND umbracoNode.id <> @id",
}
}
+ ///
+ private void CopyTagData(int? sourceLanguageId, int? targetLanguageId, IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null)
+ {
+ // note: important to use NEquals for nullable types, cannot directly compare language identifiers
+
+ // fixme - should we batch then?
+ var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
+ if (whereInArgsCount > 2000)
+ throw new NotSupportedException("Too many property/content types.");
+
+ // delete existing relations (for target language)
+ // do *not* delete existing tags
+
+ var sqlTagToDelete = Sql()
+ .Select(x => x.Id)
+ .From()
+ .InnerJoin().On((tag, rel) => tag.Id == rel.TagId);
+
+ if (contentTypeIds != null)
+ sqlTagToDelete
+ .InnerJoin().On((rel, content) => rel.NodeId == content.NodeId)
+ .WhereIn(x => x.ContentTypeId, contentTypeIds);
+
+ sqlTagToDelete
+ .WhereIn(x => x.PropertyTypeId, propertyTypeIds)
+ .Where(x => x.LanguageId.NEquals(targetLanguageId, -1));
+
+ var sqlDeleteRel = Sql()
+ .Delete()
+ .WhereIn(x => x.TagId, sqlTagToDelete);
+
+ sqlDeleteRel.WriteToConsole();
+ Database.Execute(sqlDeleteRel);
+
+ // do *not* delete the tags - they could be used by other content types / property types
+ /*
+ var sqlDeleteTag = Sql()
+ .Delete()
+ .WhereIn(x => x.Id, sqlTagToDelete);
+ Database.Execute(sqlDeleteTag);
+ */
+
+ // copy tags from source language to target language
+
+ var targetLanguageIdS = targetLanguageId.HasValue ? targetLanguageId.ToString() : "NULL";
+ var sqlSelect = Sql()
+ .Select(x => x.Text, x => x.Group)
+ .Append(", " + targetLanguageIdS)
+ .From();
+
+ sqlSelect
+ .InnerJoin().On((tag, rel) => tag.Id == rel.TagId)
+ .LeftJoin("xtags").On((tag, xtag) => tag.Text == xtag.Text && tag.Group == xtag.Group && tag.LanguageId.NEquals(targetLanguageId, -1), aliasRight: "xtags");
+
+ if (contentTypeIds != null)
+ sqlSelect
+ .InnerJoin().On((rel, content) => rel.NodeId == content.NodeId);
+
+ sqlSelect
+ .WhereIn(x => x.PropertyTypeId, propertyTypeIds)
+ .WhereNull(x => x.Id, "xtags"); // ie, not exists
+
+ if (contentTypeIds != null)
+ sqlSelect
+ .WhereIn(x => x.ContentTypeId, contentTypeIds);
+
+ sqlSelect.Where(x => x.LanguageId.NEquals(sourceLanguageId, -1));
+
+ var cols = Sql().Columns(x => x.Text, x => x.Group, x => x.LanguageId);
+ var sqlInsertTag = Sql($"INSERT INTO {TagDto.TableName} ({cols})").Append(sqlSelect);
+
+ sqlInsertTag.WriteToConsole();
+ Database.Execute(sqlInsertTag);
+
+ // create relations to new tags
+
+ var sqlFoo = Sql()
+ .Select(x => x.NodeId, x => x.PropertyTypeId)
+ .AndSelect("otag", x => x.Id)
+ .From()
+ .InnerJoin().On((rel, tag) => rel.TagId == tag.Id)
+ .InnerJoin("otag").On((tag, otag) => tag.Text == otag.Text && tag.Group == otag.Group && otag.LanguageId.NEquals(targetLanguageId, -1), aliasRight: "otag")
+ .Where(x => x.LanguageId.NEquals(sourceLanguageId, -1));
+
+ var cols2 = Sql().Columns(x => x.NodeId, x => x.PropertyTypeId, x => x.TagId);
+ var sqlInsertRel = Sql($"INSERT INTO {TagRelationshipDto.TableName} ({cols2})").Append(sqlFoo);
+
+ sqlInsertRel.WriteToConsole();
+ Database.Execute(sqlInsertRel);
+
+ // delete original relations - *not* the tags - all of them
+ // cannot really "go back" with relations, would have to do it with property values
+
+ sqlTagToDelete = Sql()
+ .Select(x => x.Id)
+ .From()
+ .InnerJoin().On((tag, rel) => tag.Id == rel.TagId);
+
+ if (contentTypeIds != null)
+ sqlTagToDelete
+ .InnerJoin().On((rel, content) => rel.NodeId == content.NodeId)
+ .WhereIn(x => x.ContentTypeId, contentTypeIds);
+
+ sqlTagToDelete
+ .WhereIn(x => x.PropertyTypeId, propertyTypeIds)
+ .Where(x => !x.LanguageId.NEquals(targetLanguageId, -1));
+
+ sqlDeleteRel = Sql()
+ .Delete()
+ .WhereIn(x => x.TagId, sqlTagToDelete);
+
+ sqlDeleteRel.WriteToConsole();
+ Database.Execute(sqlDeleteRel);
+
+ // no
+ /*
+ var sqlDeleteTag = Sql()
+ .Delete()
+ .WhereIn(x => x.Id, sqlTagToDelete);
+ Database.Execute(sqlDeleteTag);
+ */
+ }
+
///
/// Copies property data from one language to another.
///
@@ -766,6 +891,8 @@ AND umbracoNode.id <> @id",
/// The content type identifiers.
private void CopyPropertyData(int? sourceLanguageId, int? targetLanguageId, IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null)
{
+ // note: important to use NEquals for nullable types, cannot directly compare language identifiers
+ //
// fixme - should we batch then?
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
if (whereInArgsCount > 2000)
@@ -793,11 +920,7 @@ AND umbracoNode.id <> @id",
sqlDelete.WhereIn(x => x.VersionId, inSql);
}
- // NPoco cannot turn the clause into IS NULL with a nullable parameter - deal with it
- if (targetLanguageId == null)
- sqlDelete.Where(x => x.LanguageId == null);
- else
- sqlDelete.Where(x => x.LanguageId == targetLanguageId);
+ sqlDelete.Where(x => x.LanguageId.NEquals(targetLanguageId, -1));
sqlDelete
.WhereIn(x => x.PropertyTypeId, propertyTypeIds);
@@ -821,11 +944,7 @@ AND umbracoNode.id <> @id",
.InnerJoin().On((pdata, cversion) => pdata.VersionId == cversion.Id)
.InnerJoin().On((cversion, c) => cversion.NodeId == c.NodeId);
- // NPoco cannot turn the clause into IS NULL with a nullable parameter - deal with it
- if (sourceLanguageId == null)
- sqlSelectData.Where(x => x.LanguageId == null);
- else
- sqlSelectData.Where(x => x.LanguageId == sourceLanguageId);
+ sqlSelectData.Where(x => x.LanguageId.NEquals(sourceLanguageId, -1));
sqlSelectData
.WhereIn(x => x.PropertyTypeId, propertyTypeIds);
diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
index 64e4c0adca..2c07dff63c 100644
--- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
+++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
@@ -67,6 +67,24 @@ namespace Umbraco.Core.Persistence
///
public ISqlContext SqlContext { get; }
+ #region Temp
+
+ // work around NPoco issue https://github.com/schotime/NPoco/issues/517 while we wait for the fix
+ public override DbCommand CreateCommand(DbConnection connection, CommandType commandType, string sql, params object[] args)
+ {
+ var command = base.CreateCommand(connection, commandType, sql, args);
+
+ if (!DatabaseType.IsSqlCe()) return command;
+
+ foreach (DbParameter parameter in command.Parameters)
+ if (parameter.Value == DBNull.Value)
+ parameter.DbType = DbType.String;
+
+ return command;
+ }
+
+ #endregion
+
#region Testing, Debugging and Troubleshooting
private bool _enableCount;
@@ -242,7 +260,9 @@ namespace Umbraco.Core.Persistence
sb.Append(" @");
sb.Append(i++);
sb.Append(":");
- sb.Append(arg);
+ if (arg == DBNull.Value) sb.Append("");
+ else if (arg == null) sb.Append("");
+ else sb.Append(arg);
}
return sb.ToString();
diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs b/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs
index 458479b293..9fbba01e94 100644
--- a/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs
+++ b/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using NPoco;
using NUnit.Framework;
+using Umbraco.Core;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Tests.TestHelpers;
@@ -11,6 +12,80 @@ namespace Umbraco.Tests.Persistence.NPocoTests
[TestFixture]
public class NPocoSqlExtensionsTests : BaseUsingSqlCeSyntax
{
+ [Test]
+ public void WhereTest()
+ {
+ var sql = new Sql(SqlContext)
+ .Select("*")
+ .From()
+ .Where(x => x.LanguageId == null);
+ Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] is null))", sql.SQL, sql.SQL);
+
+ sql = new Sql(SqlContext)
+ .Select("*")
+ .From()
+ .Where(x => x.LanguageId == 123);
+ Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] = @0))", sql.SQL, sql.SQL);
+
+ var id = 123;
+
+ sql = new Sql(SqlContext)
+ .Select("*")
+ .From()
+ .Where(x => x.LanguageId == id);
+ Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] = @0))", sql.SQL, sql.SQL);
+
+ int? nid = 123;
+
+ sql = new Sql(SqlContext)
+ .Select("*")
+ .From()
+ .Where(x => x.LanguageId == nid);
+ Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] = @0))", sql.SQL, sql.SQL);
+
+ // but the above comparison fails if @0 is null
+ // what we want is something similar to:
+
+ sql = new Sql(SqlContext)
+ .Select("*")
+ .From()
+ .Where(x => (nid == null && x.LanguageId == null) || (nid != null && x.LanguageId == nid));
+ Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((((@0 is null) AND ([umbracoPropertyData].[languageId] is null)) OR ((@1 is not null) AND ([umbracoPropertyData].[languageId] = @2))))", sql.SQL, sql.SQL);
+
+ // new NEquals method does it automatically
+ // 'course it would be nicer if '==' could do it
+ // see note in ExpressionVisitorBase for NEquals
+
+ //sql = new Sql(SqlContext)
+ // .Select("*")
+ // .From()
+ // .Where(x => x.LanguageId.NEquals(nid));
+ //Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((((@0 is null) AND ([umbracoPropertyData].[languageId] is null)) OR ((@0 is not null) AND ([umbracoPropertyData].[languageId] = @0))))", sql.SQL, sql.SQL);
+
+ // but, the expression above fails with SQL CE, 'specified argument for the function is not valid' in 'isnull' function
+ // so... compare with fallback values
+
+ sql = new Sql(SqlContext)
+ .Select("*")
+ .From()
+ .Where(x => x.LanguageId.NEquals(nid, -1));
+ Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((COALESCE([umbracoPropertyData].[languageId],@0) = COALESCE(@1,@0)))", sql.SQL, sql.SQL);
+ }
+
+ [Test]
+ public void NEqualsTest()
+ {
+ int? a, b;
+ a = b = null;
+ Assert.IsTrue(a.NEquals(b, -1));
+ b = 2;
+ Assert.IsFalse(a.NEquals(b, -1));
+ a = 2;
+ Assert.IsTrue(a.NEquals(b, -1));
+ b = null;
+ Assert.IsFalse(a.NEquals(b, -1));
+ }
+
[Test]
public void WhereInValueFieldTest()
{
diff --git a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs
index c05bde4c7c..071fa99264 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs
@@ -129,6 +129,133 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
}
+ [Test]
+ public void TagsCanBecomeVariant()
+ {
+ var enId = ServiceContext.LocalizationService.GetLanguageIdByIsoCode("en-US").Value;
+
+ var contentService = ServiceContext.ContentService;
+ var contentTypeService = ServiceContext.ContentTypeService;
+ var tagService = ServiceContext.TagService;
+ var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
+ PropertyType propertyType;
+ contentType.PropertyGroups.First().PropertyTypes.Add(
+ propertyType = new PropertyType("test", ValueStorageType.Ntext, "tags")
+ {
+ DataTypeId = 1041
+ });
+ contentTypeService.Save(contentType);
+
+ IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
+ content1.AssignTags("tags", new[] { "hello", "world", "another", "one" });
+ contentService.SaveAndPublish(content1);
+
+ contentType.Variations = ContentVariation.Culture;
+ contentTypeService.Save(contentType);
+
+ // no changes
+ content1 = contentService.GetById(content1.Id);
+
+ var tags = content1.Properties["tags"].GetTagsValue().ToArray();
+ Assert.AreEqual(4, tags.Length);
+ Assert.Contains("one", tags);
+ Assert.AreEqual(-1, tags.IndexOf("plus"));
+
+ var tagGroups = tagService.GetAllTags().GroupBy(x => x.LanguageId);
+ foreach (var tag in tagService.GetAllTags())
+ Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
+ Assert.AreEqual(1, tagGroups.Count());
+ var enTagGroup = tagGroups.FirstOrDefault(x => x.Key == null);
+ Assert.IsNotNull(enTagGroup);
+ Assert.AreEqual(4, enTagGroup.Count());
+ Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
+ Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
+
+ propertyType.Variations = ContentVariation.Culture;
+ contentTypeService.Save(contentType);
+
+ // changes
+ content1 = contentService.GetById(content1.Id);
+
+ // property value has been moved from invariant to en-US
+ tags = content1.Properties["tags"].GetTagsValue().ToArray();
+ Assert.IsEmpty(tags);
+
+ tags = content1.Properties["tags"].GetTagsValue("en-US").ToArray();
+ Assert.AreEqual(4, tags.Length);
+ Assert.Contains("one", tags);
+ Assert.AreEqual(-1, tags.IndexOf("plus"));
+
+ // tags have been copied from invariant to en-US
+ tagGroups = tagService.GetAllTags(culture: "*").GroupBy(x => x.LanguageId);
+ foreach (var tag in tagService.GetAllTags("*"))
+ Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
+ Assert.AreEqual(1, tagGroups.Count());
+
+ enTagGroup = tagGroups.FirstOrDefault(x => x.Key == enId);
+ Assert.IsNotNull(enTagGroup);
+ Assert.AreEqual(4, enTagGroup.Count());
+ Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
+ Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
+ }
+
+ [Test]
+ public void TagsCanBecomeInvariant()
+ {
+ var languageService = ServiceContext.LocalizationService;
+ languageService.Save(new Language("fr-FR")); // en-US is already there
+
+ var enId = ServiceContext.LocalizationService.GetLanguageIdByIsoCode("en-US").Value;
+
+ var contentService = ServiceContext.ContentService;
+ var contentTypeService = ServiceContext.ContentTypeService;
+ var tagService = ServiceContext.TagService;
+ var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
+ PropertyType propertyType;
+ contentType.PropertyGroups.First().PropertyTypes.Add(
+ propertyType = new PropertyType("test", ValueStorageType.Ntext, "tags")
+ {
+ DataTypeId = 1041,
+ Variations = ContentVariation.Culture
+ });
+ contentType.Variations = ContentVariation.Culture;
+ contentTypeService.Save(contentType);
+
+ IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
+ content1.SetCultureName("name-fr", "fr-FR");
+ content1.SetCultureName("name-en", "en-US");
+ content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }, culture: "fr-FR");
+ content1.AssignTags("tags", new[] { "hello", "world", "another", "one" }, culture: "en-US");
+ contentService.SaveAndPublish(content1);
+
+ contentType.Variations = ContentVariation.Nothing;
+ contentTypeService.Save(contentType);
+
+ // changes
+ content1 = contentService.GetById(content1.Id);
+
+ // property value has been moved from en-US to invariant, fr-FR tags are gone
+ Assert.IsEmpty(content1.Properties["tags"].GetTagsValue("fr-FR"));
+ Assert.IsEmpty(content1.Properties["tags"].GetTagsValue("en-US"));
+
+ var tags = content1.Properties["tags"].GetTagsValue().ToArray();
+ Assert.AreEqual(4, tags.Length);
+ Assert.Contains("one", tags);
+ Assert.AreEqual(-1, tags.IndexOf("plus"));
+
+ // tags have been copied from en-US to invariant, fr-FR tags are gone
+ var tagGroups = tagService.GetAllTags(culture: "*").GroupBy(x => x.LanguageId);
+ foreach (var tag in tagService.GetAllTags("*"))
+ Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
+ Assert.AreEqual(1, tagGroups.Count());
+
+ var enTagGroup = tagGroups.FirstOrDefault(x => x.Key == null);
+ Assert.IsNotNull(enTagGroup);
+ Assert.AreEqual(4, enTagGroup.Count());
+ Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
+ Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
+ }
+
[Test]
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_One()
{
@@ -193,7 +320,7 @@ namespace Umbraco.Tests.Services
}
[Test]
- [Ignore("U4-8442, will need to be fixed eventually.")]
+ //[Ignore("U4-8442, will need to be fixed eventually.")]
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_Tree()
{
var contentService = ServiceContext.ContentService;
@@ -209,12 +336,10 @@ namespace Umbraco.Tests.Services
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" });
- content1.PublishCulture();
contentService.SaveAndPublish(content1);
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1.Id);
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
- content2.PublishCulture();
contentService.SaveAndPublish(content2);
// verify
@@ -298,7 +423,7 @@ namespace Umbraco.Tests.Services
}
[Test]
- [Ignore("U4-8442, will need to be fixed eventually.")]
+ //[Ignore("U4-8442, will need to be fixed eventually.")]
public void TagsAreUpdatedWhenContentIsUnpublishedAndRePublished_Tree()
{
var contentService = ServiceContext.ContentService;
@@ -314,12 +439,10 @@ namespace Umbraco.Tests.Services
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" });
- content1.PublishCulture();
contentService.SaveAndPublish(content1);
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1);
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
- content2.PublishCulture();
contentService.SaveAndPublish(content2);
contentService.Unpublish(content1);