From c5ba23a15c360eada010553c39809d8dbf26ba1e Mon Sep 17 00:00:00 2001
From: Ronald Barendse
Date: Wed, 13 Oct 2021 13:32:30 +0200
Subject: [PATCH 01/51] Add Constants.Sql.MaxParameterCount
---
src/Umbraco.Core/Constants-Sql.cs | 17 +++++++++++++++++
src/Umbraco.Core/Umbraco.Core.csproj | 3 ++-
2 files changed, 19 insertions(+), 1 deletion(-)
create mode 100644 src/Umbraco.Core/Constants-Sql.cs
diff --git a/src/Umbraco.Core/Constants-Sql.cs b/src/Umbraco.Core/Constants-Sql.cs
new file mode 100644
index 0000000000..366dfbe44a
--- /dev/null
+++ b/src/Umbraco.Core/Constants-Sql.cs
@@ -0,0 +1,17 @@
+namespace Umbraco.Core
+{
+ public static partial class Constants
+ {
+ public static class Sql
+ {
+ ///
+ /// The maximum amount of parameters that can be used in a query.
+ ///
+ ///
+ /// The actual limit is 2100 (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server),
+ /// but we want to ensure there's room for additional parameters if this value is used to create groups/batches.
+ ///
+ public const int MaxParameterCount = 2000;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 77eeaaa853..e2679c2223 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -132,6 +132,7 @@
+
@@ -1665,4 +1666,4 @@
-
+
\ No newline at end of file
From a6b04a941ced30381230c3d37e0c667960a2bfda Mon Sep 17 00:00:00 2001
From: Ronald Barendse
Date: Wed, 13 Oct 2021 13:42:18 +0200
Subject: [PATCH 02/51] Replace arbitrary group size with constant
---
.../Persistence/NPocoDatabaseExtensions-Bulk.cs | 2 +-
.../Repositories/Implement/AuditEntryRepository.cs | 2 +-
.../Repositories/Implement/ContentRepositoryBase.cs | 4 ++--
.../Repositories/Implement/ContentTypeRepositoryBase.cs | 8 ++++----
.../Repositories/Implement/DictionaryRepository.cs | 3 +--
.../Repositories/Implement/DocumentRepository.cs | 6 +++---
.../Repositories/Implement/EntityContainerRepository.cs | 2 +-
.../Repositories/Implement/EntityRepository.cs | 2 +-
.../Repositories/Implement/PermissionRepository.cs | 9 ++++-----
.../Repositories/Implement/RedirectUrlRepository.cs | 5 +++--
.../Repositories/Implement/RepositoryBaseOfTIdTEntity.cs | 5 ++---
.../Persistence/Repositories/Implement/TagRepository.cs | 2 +-
src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs | 2 +-
13 files changed, 25 insertions(+), 27 deletions(-)
diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
index 77cc0d6601..a50e3c2aaa 100644
--- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
+++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
@@ -137,7 +137,7 @@ namespace Umbraco.Core.Persistence
// Math.Floor(2100 / 8) = 262 record per command
// 4168 / 262 = 15.908... = there will be 16 command in total
// (if we have disabled db parameters, then all records will be included, in only one command)
- var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRecord));
+ var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor((double)Constants.Sql.MaxParameterCount / paramsPerRecord));
var commandsCount = Convert.ToInt32(Math.Ceiling((double)records.Length / recordsPerCommand));
var commands = new IDbCommand[commandsCount];
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs
index c3d34cc3e9..d921bb6d51 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs
@@ -54,7 +54,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var entries = new List();
- foreach (var group in ids.InGroupsOf(2000))
+ foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
{
var sql = Sql()
.Select()
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 85814ef681..a6b08c2bfd 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -638,7 +638,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// in the table?
// get all PropertyDataDto for all definitions / versions
- var allPropertyDataDtos = Database.FetchByGroups(versions, 2000, batch =>
+ var allPropertyDataDtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch =>
SqlContext.Sql()
.Select()
.From()
@@ -647,7 +647,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// get PropertyDataDto distinct PropertyTypeDto
var allPropertyTypeIds = allPropertyDataDtos.Select(x => x.PropertyTypeId).Distinct().ToList();
- var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, 2000, batch =>
+ var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, Constants.Sql.MaxParameterCount, batch =>
SqlContext.Sql()
.Select(r => r.Select(x => x.DataTypeDto))
.From()
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index aff53e44b0..8835f8c647 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -768,7 +768,7 @@ AND umbracoNode.id <> @id",
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
// delete existing relations (for target language)
@@ -906,7 +906,7 @@ AND umbracoNode.id <> @id",
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
//
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
//first clear out any existing property data that might already exists under the target language
@@ -1005,7 +1005,7 @@ AND umbracoNode.id <> @id",
//based on the current variance of each item to see if it's 'edited' value should be true/false.
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
var propertySql = Sql()
@@ -1095,7 +1095,7 @@ AND umbracoNode.id <> @id",
}
//lookup all matching rows in umbracoDocumentCultureVariation
- var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
+ var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(Constants.Sql.MaxParameterCount)
.SelectMany(_ => Database.Fetch(
Sql().Select().From()
.WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
index ac1f7c3f2a..7895b7aa8f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
@@ -260,8 +260,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Func>> getItemsFromParents = guids =>
{
- //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used.
- return guids.InGroupsOf(2000)
+ return guids.InGroupsOf(Constants.Sql.MaxParameterCount)
.Select(@group =>
{
var sqlClause = GetBaseQuery(false)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index 5716fbe129..753630d186 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -1342,7 +1342,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var result = new Dictionary();
- var scheduleDtos = Database.FetchByGroups(contentIds, 2000, batch => Sql()
+ var scheduleDtos = Database.FetchByGroups(contentIds, Constants.Sql.MaxParameterCount, batch => Sql()
.Select()
.From()
.WhereIn(x => x.NodeId, batch));
@@ -1391,7 +1391,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
if (versions.Count == 0) return new Dictionary>();
- var dtos = Database.FetchByGroups(versions, 2000, batch
+ var dtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch
=> Sql()
.Select()
.From()
@@ -1420,7 +1420,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var ids = temps.Select(x => x.Id);
- var dtos = Database.FetchByGroups(ids, 2000, batch =>
+ var dtos = Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch =>
Sql()
.Select()
.From()
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs
index 505cbfc816..07b82190f5 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs
@@ -60,7 +60,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
if (ids.Any())
{
- return Database.FetchByGroups(ids, 2000, batch =>
+ return Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch =>
GetBaseQuery(false)
.Where(x => x.NodeObjectType == NodeObjectTypeId)
.WhereIn(x => x.NodeId, batch))
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
index a7502a338a..7bcc5f6b2d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
@@ -281,7 +281,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (v == null) return entitiesList;
// fetch all variant info dtos
- var dtos = Database.FetchByGroups(v.Select(x => x.Id), 2000, GetVariantInfos);
+ var dtos = Database.FetchByGroups(v.Select(x => x.Id), Constants.Sql.MaxParameterCount, GetVariantInfos);
// group by node id (each group contains all languages)
var xdtos = dtos.GroupBy(x => x.NodeId).ToDictionary(x => x.Key, x => x);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs
index 259f0b89c0..ce324df685 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs
@@ -44,7 +44,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var result = new EntityPermissionCollection();
- foreach (var groupOfGroupIds in groupIds.InGroupsOf(2000))
+ foreach (var groupOfGroupIds in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
//copy local
var localIds = groupOfGroupIds.ToArray();
@@ -64,7 +64,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
else
{
//iterate in groups of 2000 since we don't want to exceed the max SQL param count
- foreach (var groupOfEntityIds in entityIds.InGroupsOf(2000))
+ foreach (var groupOfEntityIds in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
var ids = groupOfEntityIds;
var sql = Sql()
@@ -133,11 +133,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var db = AmbientScope.Database;
- //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)";
- foreach (var idGroup in entityIds.InGroupsOf(2000))
+ foreach (var group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
- db.Execute(sql, new { groupId, nodeIds = idGroup });
+ db.Execute(sql, new { groupId, nodeIds = group });
}
var toInsert = new List();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs
index 24c1e31c20..099d49fbf8 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs
@@ -37,8 +37,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override IEnumerable PerformGetAll(params Guid[] ids)
{
- if (ids.Length > 2000)
- throw new NotSupportedException("This repository does not support more than 2000 ids.");
+ if (ids.Length > Constants.Sql.MaxParameterCount)
+ throw new NotSupportedException($"This repository does not support more than {Constants.Sql.MaxParameterCount} ids.");
+
var sql = GetBaseQuery(false).WhereIn(x => x.Id, ids);
var dtos = Database.Fetch(sql);
return dtos.WhereNotNull().Select(Map);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
index 69e4db5940..c2aec05fa7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
@@ -186,14 +186,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
// the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
- const int maxParams = 2000;
- if (ids.Length <= maxParams)
+ if (ids.Length <= Constants.Sql.MaxParameterCount)
{
return CachePolicy.GetAll(ids, PerformGetAll);
}
var entities = new List();
- foreach (var groupOfIds in ids.InGroupsOf(maxParams))
+ foreach (var groupOfIds in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
{
entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs
index 279e03b19e..83170d9dbc 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs
@@ -39,7 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var dtos = ids.Length == 0
? Database.Fetch(Sql().Select().From())
- : Database.FetchByGroups(ids, 2000, batch => Sql().Select().From().WhereIn(x => x.Id, batch));
+ : Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch => Sql().Select().From().WhereIn(x => x.Id, batch));
return dtos.Select(TagFactory.BuildEntity).ToList();
}
diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs
index 6198630898..f66a1ee934 100644
--- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs
+++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs
@@ -240,7 +240,7 @@ namespace Umbraco.Web.Models.Mapping
// the entity service due to too many Sql parameters.
var list = new List();
- foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000))
+ foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount))
list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray()));
contentEntities = list.ToArray();
}
From 88a20cb13cbde845829cbe00efa581f9737713e4 Mon Sep 17 00:00:00 2001
From: Ronald Barendse
Date: Wed, 13 Oct 2021 13:46:51 +0200
Subject: [PATCH 03/51] Minor query cleanup
---
.../Repositories/Implement/DictionaryRepository.cs | 4 ++--
.../Repositories/Implement/MemberRepository.cs | 11 ++++-------
.../Implement/RepositoryBaseOfTIdTEntity.cs | 6 ++++--
3 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
index 7895b7aa8f..da957a7288 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
@@ -261,11 +261,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Func>> getItemsFromParents = guids =>
{
return guids.InGroupsOf(Constants.Sql.MaxParameterCount)
- .Select(@group =>
+ .Select(group =>
{
var sqlClause = GetBaseQuery(false)
.Where(x => x.Parent != null)
- .Where($"{SqlSyntax.GetQuotedColumnName("parent")} IN (@parentIds)", new { parentIds = @group });
+ .WhereIn(x => x.Parent, group);
var translator = new SqlTranslator(sqlClause, Query());
var sql = translator.Translate();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
index 62f41aa727..1982d6ebf9 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
@@ -431,16 +431,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var matchedMembers = Get(query).ToArray();
var membersInGroup = new List();
- //then we need to filter the matched members that are in the role
- //since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches
- var inGroups = matchedMembers.InGroupsOf(1000);
- foreach (var batch in inGroups)
- {
- var memberIdBatch = batch.Select(x => x.Id);
+ //then we need to filter the matched members that are in the role
+ foreach (var group in matchedMembers.Select(x => x.Id).InGroupsOf(Constants.Sql.MaxParameterCount))
+ {
var sql = Sql().SelectAll().From()
.Where(dto => dto.MemberGroup == memberGroup.Id)
- .WhereIn(dto => dto.Member, memberIdBatch);
+ .WhereIn(dto => dto.Member, group);
var memberIdsInGroup = Database.Fetch(sql)
.Select(x => x.Member).ToArray();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
index c2aec05fa7..5892f2bf49 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
@@ -188,14 +188,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
if (ids.Length <= Constants.Sql.MaxParameterCount)
{
+ // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
return CachePolicy.GetAll(ids, PerformGetAll);
}
var entities = new List();
- foreach (var groupOfIds in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
+ foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
{
- entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
+ entities.AddRange(CachePolicy.GetAll(group.ToArray(), PerformGetAll));
}
+
return entities;
}
From 1bc3290497626ebe720806c4af1b62820d12a665 Mon Sep 17 00:00:00 2001
From: Ronald Barendse
Date: Wed, 13 Oct 2021 14:10:24 +0200
Subject: [PATCH 04/51] Fix bugs in fetching more than 2000 items (permissions
and culture variations)
---
.../Implement/ContentTypeRepositoryBase.cs | 22 +++++++----
.../Implement/PermissionRepository.cs | 37 +++++++++----------
2 files changed, 31 insertions(+), 28 deletions(-)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 8835f8c647..3a74b9209b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -1094,14 +1094,20 @@ AND umbracoNode.id <> @id",
}
}
- //lookup all matching rows in umbracoDocumentCultureVariation
- var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(Constants.Sql.MaxParameterCount)
- .SelectMany(_ => Database.Fetch(
- Sql().Select().From()
- .WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
- .WhereIn(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
- //convert to dictionary with the same key type
- .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
+ // lookup all matching rows in umbracoDocumentCultureVariation
+ // fetch in batches to account for maximum parameter count (distinct languages can't exceed 2000)
+ var languageIds = editedLanguageVersions.Keys.Select(x => x.langId).Distinct().ToArray();
+ var nodeIds = editedLanguageVersions.Keys.Select(x => x.nodeId).Distinct();
+ var docCultureVariationsToUpdate = nodeIds.InGroupsOf(Constants.Sql.MaxParameterCount - languageIds.Length)
+ .SelectMany(group =>
+ {
+ var sql = Sql().Select().From()
+ .WhereIn(x => x.LanguageId, languageIds)
+ .WhereIn(x => x.NodeId, group);
+
+ return Database.Fetch(sql);
+ })
+ .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); //convert to dictionary with the same key type
var toUpdate = new List();
foreach (var ev in editedLanguageVersions)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs
index ce324df685..735496a0d1 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs
@@ -38,44 +38,41 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
///
///
- /// This method will not support passing in more than 2000 group Ids
+ /// This method will not support passing in more than 2000 group IDs when also passing in entity IDs.
///
public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, params int[] entityIds)
{
var result = new EntityPermissionCollection();
- foreach (var groupOfGroupIds in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount))
+ if (entityIds.Length == 0)
{
- //copy local
- var localIds = groupOfGroupIds.ToArray();
-
- if (entityIds.Length == 0)
+ foreach (var group in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
var sql = Sql()
.SelectAll()
.From()
- .Where(dto => localIds.Contains(dto.UserGroupId));
+ .Where(dto => group.Contains(dto.UserGroupId));
+
var permissions = AmbientScope.Database.Fetch(sql);
foreach (var permission in ConvertToPermissionList(permissions))
{
result.Add(permission);
}
}
- else
+ }
+ else
+ {
+ foreach (var group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - groupIds.Length))
{
- //iterate in groups of 2000 since we don't want to exceed the max SQL param count
- foreach (var groupOfEntityIds in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount))
+ var sql = Sql()
+ .SelectAll()
+ .From()
+ .Where(dto => groupIds.Contains(dto.UserGroupId) && group.Contains(dto.NodeId));
+
+ var permissions = AmbientScope.Database.Fetch(sql);
+ foreach (var permission in ConvertToPermissionList(permissions))
{
- var ids = groupOfEntityIds;
- var sql = Sql()
- .SelectAll()
- .From()
- .Where(dto => localIds.Contains(dto.UserGroupId) && ids.Contains(dto.NodeId));
- var permissions = AmbientScope.Database.Fetch(sql);
- foreach (var permission in ConvertToPermissionList(permissions))
- {
- result.Add(permission);
- }
+ result.Add(permission);
}
}
}
From 60be71d1a0037ffb5556511342065f85afce2825 Mon Sep 17 00:00:00 2001
From: Ronald Barendse
Date: Wed, 13 Oct 2021 23:34:50 +0200
Subject: [PATCH 05/51] Remove duplicated comment
---
.../Repositories/Implement/RepositoryBaseOfTIdTEntity.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
index 5892f2bf49..a7704272a8 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
@@ -188,7 +188,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
if (ids.Length <= Constants.Sql.MaxParameterCount)
{
- // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
return CachePolicy.GetAll(ids, PerformGetAll);
}
From b13f2603412dc62536d759ab67ae4ed7e4f7feb2 Mon Sep 17 00:00:00 2001
From: Patrick de Mooij
Date: Mon, 11 Oct 2021 21:15:25 +0200
Subject: [PATCH 06/51] 11343: Remove blocklist block on cancel
---
.../blocklist/umbBlockListPropertyEditor.component.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
index 7334fbeadf..11ef37029c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
@@ -65,6 +65,9 @@
vm.layout = []; // The layout object specific to this Block Editor, will be a direct reference from Property Model.
vm.availableBlockTypes = []; // Available block entries of this property editor.
vm.labels = {};
+ vm.options = {
+ createFlow: false
+ };
localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) {
vm.labels.grid_addElement = data[0];
@@ -380,7 +383,7 @@
function editBlock(blockObject, openSettings, blockIndex, parentForm, options) {
- options = options || {};
+ options = options || vm.options;
// this must be set
if (blockIndex === undefined) {
@@ -560,7 +563,9 @@
if (inlineEditing === true) {
blockObject.activate();
} else if (inlineEditing === false && blockObject.hideContentInOverlay !== true) {
+ vm.options.createFlow = true;
blockObject.edit();
+ vm.options.createFlow = false;
}
}
}
From 809671b9caa4355d086afb8abeaec041287640f4 Mon Sep 17 00:00:00 2001
From: patrickdemooij9
Date: Thu, 21 Oct 2021 02:09:08 +0200
Subject: [PATCH 07/51] Align template picking more towards the other pickers
(#11363)
* Align template picking more towards the other pickers
* style/cleanup - remove unused args, updated openItemPicker function name to openTemplatePicker since that's what we're opening
Co-authored-by: Nathan Woulfe
---
.../components/umbgridselector.directive.js | 49 ++++++++++---------
.../src/common/services/editor.service.js | 25 +++++++++-
.../treepicker/treepicker.controller.js | 3 ++
.../views/templates/templates.controller.js | 26 +++++++++-
.../views/templates/templates.html | 3 +-
.../src/views/templates/edit.controller.js | 26 +++++-----
src/Umbraco.Web/Editors/EntityController.cs | 18 +++++++
7 files changed, 110 insertions(+), 40 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
index bf03749faa..4c628391cb 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
@@ -1,9 +1,9 @@
(function () {
'use strict';
- function GridSelector($location, overlayService, editorService) {
+ function GridSelector(overlayService, editorService) {
- function link(scope, el, attr, ctrl) {
+ function link(scope) {
var eventBindings = [];
scope.dialogModel = {};
@@ -33,26 +33,30 @@
};
scope.openItemPicker = function ($event) {
- var dialogModel = {
- view: "itempicker",
- title: "Choose " + scope.itemLabel,
- availableItems: scope.availableItems,
- selectedItems: scope.selectedItems,
- position: "target",
- event: $event,
- submit: function (model) {
- scope.selectedItems.push(model.selectedItem);
- // if no default item - set item as default
- if (scope.defaultItem === null) {
- scope.setAsDefaultItem(model.selectedItem);
+ if (scope.itemPicker) {
+ scope.itemPicker();
+ } else {
+ var dialogModel = {
+ view: "itempicker",
+ title: "Choose " + scope.itemLabel,
+ availableItems: scope.availableItems,
+ selectedItems: scope.selectedItems,
+ position: "target",
+ event: $event,
+ submit: function (model) {
+ scope.selectedItems.push(model.selectedItem);
+ // if no default item - set item as default
+ if (scope.defaultItem === null) {
+ scope.setAsDefaultItem(model.selectedItem);
+ }
+ overlayService.close();
+ },
+ close: function () {
+ overlayService.close();
}
- overlayService.close();
- },
- close: function() {
- overlayService.close();
- }
- };
- overlayService.open(dialogModel);
+ };
+ overlayService.open(dialogModel);
+ }
};
scope.openTemplate = function (selectedItem) {
@@ -156,7 +160,8 @@
availableItems: "=",
defaultItem: "=",
itemName: "@",
- updatePlaceholder: "="
+ updatePlaceholder: "=",
+ itemPicker: "="
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
index b917d6fa4f..a68f68fbb3 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
@@ -179,7 +179,7 @@ When building a custom infinite editor view you can use the same components as a
} else {
focus();
}
- });
+ });
/**
* @ngdoc method
@@ -928,6 +928,28 @@ When building a custom infinite editor view you can use the same components as a
open(editor);
}
+ /**
+ * @ngdoc method
+ * @name umbraco.services.editorService#templatePicker
+ * @methodOf umbraco.services.editorService
+ *
+ * @description
+ * Opens a template picker in infinite editing, the submit callback returns an array of selected items.
+ *
+ * @param {object} editor rendering options.
+ * @param {boolean} editor.multiPicker Pick one or multiple items.
+ * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
+ * @param {function} editor.close Callback function when the close button is clicked.
+ * @returns {object} editor object.
+ */
+ function templatePicker(editor) {
+ editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
+ if (!editor.size) editor.size = "small";
+ editor.section = "settings";
+ editor.treeAlias = "templates";
+ open(editor);
+ }
+
/**
* @ngdoc method
* @name umbraco.services.editorService#macroPicker
@@ -1088,6 +1110,7 @@ When building a custom infinite editor view you can use the same components as a
templateSections: templateSections,
userPicker: userPicker,
itemPicker: itemPicker,
+ templatePicker: templatePicker,
macroPicker: macroPicker,
memberGroupPicker: memberGroupPicker,
memberPicker: memberPicker,
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js
index ed5c4096bc..72eb504c60 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js
@@ -141,6 +141,9 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
});
}
}
+ else if (vm.treeAlias === "templates") {
+ vm.entityType = "Template";
+ }
// TODO: Seems odd this logic is here, i don't think it needs to be and should just exist on the property editor using this
if ($scope.model.minNumber) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.controller.js
index e2a964c293..46e856410d 100644
--- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.controller.js
@@ -9,7 +9,7 @@
(function () {
'use strict';
- function TemplatesController($scope, entityResource, contentTypeHelper, templateResource, contentTypeResource, $routeParams) {
+ function TemplatesController($scope, entityResource, contentTypeHelper, contentTypeResource, editorService, $routeParams) {
/* ----------- SCOPE VARIABLES ----------- */
@@ -22,6 +22,7 @@
vm.isElement = $scope.model.isElement;
vm.createTemplate = createTemplate;
+ vm.openTemplatePicker = openTemplatePicker;
/* ---------- INIT ---------- */
@@ -81,6 +82,29 @@
vm.canCreateTemplate = existingTemplate ? false : true;
}
+ function openTemplatePicker() {
+ const editor = {
+ title: "Choose template",
+ filterCssClass: 'not-allowed',
+ multiPicker: true,
+ filter: item => {
+ return !vm.availableTemplates.some(template => template.id == item.id) ||
+ $scope.model.allowedTemplates.some(template => template.id == item.id);
+ },
+ submit: model => {
+ model.selection.forEach(item => {
+ $scope.model.allowedTemplates.push(item);
+ });
+ editorService.close();
+ },
+ close: function() {
+ editorService.close();
+ }
+ }
+
+ editorService.templatePicker(editor);
+ }
+
var unbindWatcher = $scope.$watch("model.isElement",
function(newValue, oldValue) {
vm.isElement = newValue;
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html
index 279ffb73c0..04fd61be3c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html
+++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html
@@ -17,7 +17,8 @@
item-name="template"
name="model.name"
alias="model.alias"
- update-placeholder="vm.updateTemplatePlaceholder">
+ update-placeholder="vm.updateTemplatePlaceholder"
+ item-picker="vm.openTemplatePicker">
{
+ const editor = {
+ title,
+ filterCssClass: 'not-allowed',
+ filter: item => !availableMasterTemplates.some(template => template.id == item.id),
+ submit: model => {
+ var template = model.selection[0];
if (template && template.alias) {
vm.template.masterTemplateAlias = template.alias;
setLayout(template.alias + ".cshtml");
@@ -575,14 +575,10 @@
}
editorService.close();
},
- close: function (oldModel) {
- // close dialog
- editorService.close();
- // focus editor
- vm.editor.focus();
- }
- };
- editorService.itemPicker(masterTemplate);
+ close: () => editorService.close(),
+ }
+
+ editorService.templatePicker(editor);
});
}
diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs
index 0b6273e79d..573246e1f0 100644
--- a/src/Umbraco.Web/Editors/EntityController.cs
+++ b/src/Umbraco.Web/Editors/EntityController.cs
@@ -997,6 +997,15 @@ namespace Umbraco.Web.Editors
case UmbracoEntityTypes.Macro:
+ case UmbracoEntityTypes.Template:
+ var template = Services.FileService.GetTemplate(key);
+ if (template is null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ return Mapper.Map(template);
+
default:
throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
}
@@ -1029,6 +1038,15 @@ namespace Umbraco.Web.Editors
case UmbracoEntityTypes.Macro:
+ case UmbracoEntityTypes.Template:
+ var template = Services.FileService.GetTemplate(id);
+ if (template is null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ return Mapper.Map(template);
+
default:
throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
}
From aa6c5d48a2ec61bc19dc7d00d67e004c926dcc94 Mon Sep 17 00:00:00 2001
From: BatJan <1932158+BatJan@users.noreply.github.com>
Date: Wed, 20 Oct 2021 22:30:31 +0200
Subject: [PATCH 08/51] Add fallback text
---
.../views/propertyeditors/grid/overlays/rowdeleteconfirm.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html
index 2ba56a5b88..ded2e5b940 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html
@@ -11,6 +11,6 @@
- ?
+ Are you sure you want to delete?
From 00d7422d2fe894810c50a242b3cf07e020a4f678 Mon Sep 17 00:00:00 2001
From: Paul Seal
Date: Fri, 22 Oct 2021 02:47:35 +0100
Subject: [PATCH 09/51] V8/feature/app header localized titles (#11429)
* Added a link to 404 documentation
I think it will be useful to include a link to the documentation on how to create a custom 404
* added localized titles to the search and help buttons in the app header (top right of Umbraco)
---
.../src/views/components/application/umb-app-header.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html
index 3ad4ebc188..e0fb4aeb77 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html
@@ -10,7 +10,7 @@
-
-
+ Open/Close backoffice help...
From 79e820555686f4ef7731a8b0a1987aa0be983219 Mon Sep 17 00:00:00 2001
From: BatJan <1932158+BatJan@users.noreply.github.com>
Date: Fri, 22 Oct 2021 08:31:13 +0200
Subject: [PATCH 10/51] Remove comma
---
.../src/views/templates/edit.controller.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
index 5a0f49834a..856886a870 100644
--- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
@@ -575,7 +575,7 @@
}
editorService.close();
},
- close: () => editorService.close(),
+ close: () => editorService.close()
}
editorService.templatePicker(editor);
From 76879caaaae85a06ec21ae370fab5e236c3e0cc6 Mon Sep 17 00:00:00 2001
From: Paul Johnson
Date: Mon, 25 Oct 2021 16:31:16 +0100
Subject: [PATCH 11/51] Prevent github actions spam
---
.github/{workflows => config}/codeql-config.yml | 0
.github/workflows/codeql-analysis.yml | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename .github/{workflows => config}/codeql-config.yml (100%)
diff --git a/.github/workflows/codeql-config.yml b/.github/config/codeql-config.yml
similarity index 100%
rename from .github/workflows/codeql-config.yml
rename to .github/config/codeql-config.yml
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 3d08bf3a4b..ee912262d7 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -56,7 +56,7 @@ jobs:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
with:
- config-file: ./.github/workflows/codeql-config.yml
+ config-file: ./.github/config/codeql-config.yml
# This job is to prevent the workflow status from showing as failed when all other jobs are skipped - See https://github.community/t/workflow-is-failing-if-no-job-can-be-ran-due-to-condition/16873
always_job:
From 45dec5c181714c159f1776a35953d8688d986c45 Mon Sep 17 00:00:00 2001
From: BatJan <1932158+BatJan@users.noreply.github.com>
Date: Sat, 23 Oct 2021 14:44:24 +0200
Subject: [PATCH 12/51] Add missing fallack values
---
.../src/views/users/views/users/users.html | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html
index 29be782415..5569e0a985 100644
--- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html
+++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html
@@ -181,7 +181,7 @@
-
+ Sorry, we can not find what you are looking for.
@@ -305,7 +305,7 @@
Invite User
-
+ Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours.
@@ -313,7 +313,7 @@
Create user
-
+ Create new users to give them access to Umbraco. When a new user is created a password will be generated that you can share with the user.
@@ -437,7 +437,7 @@
-
+
The new user has successfully been created. To log in to Umbraco use the password below.
@@ -520,7 +520,7 @@
-
+
An invitation has been sent to the new user with details about how to log in to Umbraco.
From 123bceef085caa48817f7d3c18042f4d418a9471 Mon Sep 17 00:00:00 2001
From: Erik-Jan Westendorp
Date: Tue, 12 Oct 2021 03:50:17 +0200
Subject: [PATCH 13/51] Localization add tab text dutch (#11346)
* Update nl.xml add addTab key
* tab to tablad
* Add translation to shortcuts sections
(cherry picked from commit d22297270beacc9364b6eec39dd04f102215f010)
---
src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml
index c8f39cf020..1f27ef5fc7 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml
@@ -791,6 +791,7 @@
Blauw
+ Tabblad toevoegenGroep toevoegenEigenschap toevoegenEditor toevoegen
@@ -1530,6 +1531,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
Een documenttype kan niet worden gewijzigd in een elementtype nadat het is gebruikt om een of meer contentitems te maken.Dit is niet van toepassing op een elementtypeJe hebt wijzigingen aangebracht aan deze eigenschap. Ben je zeker dat je ze wil weggooien?
+ Tabblad toevoegenTaal toevoegen
From f9ed371cb779a13f49d15d6bf5170054f5c50bed Mon Sep 17 00:00:00 2001
From: BatJan <1932158+BatJan@users.noreply.github.com>
Date: Sat, 23 Oct 2021 15:16:20 +0200
Subject: [PATCH 14/51] Add missing fallback texts
---
.../views/propertyeditors/contentpicker/contentpicker.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html
index e93637c671..fa148ecfc7 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html
@@ -1,7 +1,7 @@
-
-
+
You have picked a content item currently deleted or in the recycle bin
+
You have picked content items currently deleted or in the recycle bin
+ The selected page in the content tree doesn't allow for any pages to be created below it.
+
-
+ There are no allowed Document Types available for creating content here. You must enable these in Document
+ Types within the Settings section, by editing the Allowed child node
+ types under Permissions.
-
+ Edit permissions for this Document Type
-
+ There are no allowed Document Types available for creating content here. You must enable these in Document
+ Types within the Settings section, by changing the Allow as root option under Permissions.
-
+ There are no Document Types available for creating content here. You must create these in Document
+ Types within the Settings section.
-
+ Valid domain names are: "example.com", "www.example.com", "example.com:8080", or "https://www.example.com/".
+ Furthermore also one-level paths in domains are supported, eg. "example.com/en" or "/en".
-
+ Domain*
-
+ Language*
@@ -49,9 +50,9 @@
-
+ Value cannot be empty
- ({{domain.other}})
+ Domain has already been assigned.({{domain.other}})