Merge branch 'main' into v17/dev
# Conflicts: # src/Umbraco.Web.UI.Client/package-lock.json
This commit is contained in:
@@ -90,6 +90,9 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.AddNotificationAsyncHandler<RuntimeUnattendedUpgradeNotification, UnattendedUpgrader>();
|
||||
builder.AddNotificationAsyncHandler<RuntimePremigrationsUpgradeNotification, PremigrationUpgrader>();
|
||||
|
||||
// Database availability check.
|
||||
builder.Services.AddUnique<IDatabaseAvailabilityCheck, DefaultDatabaseAvailabilityCheck>();
|
||||
|
||||
// Add runtime mode validation
|
||||
builder.Services.AddSingleton<IRuntimeModeValidationService, RuntimeModeValidationService>();
|
||||
builder.RuntimeModeValidators()
|
||||
@@ -124,6 +127,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
|
||||
builder.Services.AddSingleton<IJsonSerializer, SystemTextJsonSerializer>();
|
||||
builder.Services.AddSingleton<IConfigurationEditorJsonSerializer, SystemTextConfigurationEditorJsonSerializer>();
|
||||
builder.Services.AddUnique<IJsonSerializerEncoderFactory, DefaultJsonSerializerEncoderFactory>();
|
||||
builder.Services.AddUnique<IWebhookJsonSerializer, SystemTextWebhookJsonSerializer>();
|
||||
|
||||
// register database builder
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a configured database is available on boot using the default method of 5 attempts with a 1 second delay between each one.
|
||||
/// </summary>
|
||||
internal class DefaultDatabaseAvailabilityCheck : IDatabaseAvailabilityCheck
|
||||
{
|
||||
private const int NumberOfAttempts = 5;
|
||||
private const int DefaultAttemptDelayMilliseconds = 1000;
|
||||
|
||||
private readonly ILogger<DefaultDatabaseAvailabilityCheck> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultDatabaseAvailabilityCheck"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public DefaultDatabaseAvailabilityCheck(ILogger<DefaultDatabaseAvailabilityCheck> logger) => _logger = logger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of milliseconds to delay between attempts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Exposed for testing purposes, hence settable only internally.
|
||||
/// </remarks>
|
||||
public int AttemptDelayMilliseconds { get; internal set; } = DefaultAttemptDelayMilliseconds;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDatabaseAvailable(IUmbracoDatabaseFactory databaseFactory)
|
||||
{
|
||||
bool canConnect;
|
||||
for (var i = 0; ;)
|
||||
{
|
||||
canConnect = databaseFactory.CanConnect;
|
||||
if (canConnect || ++i == NumberOfAttempts)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.LogDebug("Could not immediately connect to database, trying again.");
|
||||
}
|
||||
|
||||
// Wait for the configured time before trying again.
|
||||
Thread.Sleep(AttemptDelayMilliseconds);
|
||||
}
|
||||
|
||||
return canConnect;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
[TableName(Constants.DatabaseSchema.Tables.ElementTypeTree)]
|
||||
[TableName(Constants.DatabaseSchema.Tables.ContentTypeTree)]
|
||||
[ExplicitColumns]
|
||||
internal sealed class ContentType2ContentTypeDto
|
||||
{
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a configured database is available on boot.
|
||||
/// </summary>
|
||||
public interface IDatabaseAvailabilityCheck
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the database is available for Umbraco to boot.
|
||||
/// </summary>
|
||||
/// <param name="databaseFactory">The <see cref="IUmbracoDatabaseFactory"/>.</param>
|
||||
/// <returns>
|
||||
/// A value indicating whether the database is available.
|
||||
/// </returns>
|
||||
bool IsDatabaseAvailable(IUmbracoDatabaseFactory databaseFactory);
|
||||
}
|
||||
@@ -438,7 +438,7 @@ AND umbracoNode.id <> @id",
|
||||
IEnumerable<int> propertyTypeToDeleteIds = dbPropertyTypeIds.Except(entityPropertyTypes);
|
||||
foreach (var propertyTypeId in propertyTypeToDeleteIds)
|
||||
{
|
||||
DeletePropertyType(entity.Id, propertyTypeId);
|
||||
DeletePropertyType(entity, propertyTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,7 +647,7 @@ AND umbracoNode.id <> @id",
|
||||
{
|
||||
foreach (var id in orphanPropertyTypeIds)
|
||||
{
|
||||
DeletePropertyType(entity.Id, id);
|
||||
DeletePropertyType(entity, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1410,16 +1410,27 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
}
|
||||
|
||||
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
|
||||
private void DeletePropertyType(IContentTypeComposition contentType, int propertyTypeId)
|
||||
{
|
||||
// first clear dependencies
|
||||
// First clear dependencies.
|
||||
Database.Delete<TagRelationshipDto>("WHERE propertyTypeId = @Id", new { Id = propertyTypeId });
|
||||
Database.Delete<PropertyDataDto>("WHERE propertyTypeId = @Id", new { Id = propertyTypeId });
|
||||
|
||||
// then delete the property type
|
||||
// Clear the property value permissions, which aren't a hard dependency with a foreign key, but we want to ensure
|
||||
// that any for removed property types are cleared.
|
||||
var uniqueIdAsString = string.Format(SqlContext.SqlSyntax.ConvertUniqueIdentifierToString, "uniqueId");
|
||||
var permissionSearchString = SqlContext.SqlSyntax.GetConcat(
|
||||
"(SELECT " + uniqueIdAsString + " FROM " + Constants.DatabaseSchema.Tables.PropertyType + " WHERE id = @PropertyTypeId)",
|
||||
"'|%'");
|
||||
|
||||
Database.Delete<UserGroup2GranularPermissionDto>(
|
||||
"WHERE uniqueId = @ContentTypeKey AND permission LIKE " + permissionSearchString,
|
||||
new { ContentTypeKey = contentType.Key, PropertyTypeId = propertyTypeId });
|
||||
|
||||
// Finally delete the property type.
|
||||
Database.Delete<PropertyTypeDto>(
|
||||
"WHERE contentTypeId = @Id AND id = @PropertyTypeId",
|
||||
new { Id = contentTypeId, PropertyTypeId = propertyTypeId });
|
||||
new { contentType.Id, PropertyTypeId = propertyTypeId });
|
||||
}
|
||||
|
||||
protected void ValidateAlias(TEntity entity)
|
||||
@@ -1555,20 +1566,16 @@ WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentT
|
||||
// is included here just to be 100% sure since it has a FK on cmsPropertyType.
|
||||
var list = new List<string>
|
||||
{
|
||||
"DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @id",
|
||||
"DELETE FROM umbracoUserGroup2Permission WHERE userGroupKey IN (SELECT [umbracoUserGroup].[Key] FROM umbracoUserGroup WHERE Id = @id)",
|
||||
"DELETE FROM umbracoUserGroup2GranularPermission WHERE userGroupKey IN (SELECT [umbracoUserGroup].[Key] FROM umbracoUserGroup WHERE Id = @id)",
|
||||
"DELETE FROM cmsTagRelationship WHERE nodeId = @id",
|
||||
"DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @id",
|
||||
"DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @id",
|
||||
"DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @id",
|
||||
"DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData +
|
||||
" WHERE propertyTypeId IN (SELECT id FROM cmsPropertyType WHERE contentTypeId = @id)",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyType +
|
||||
" WHERE contentTypeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyTypeGroup +
|
||||
" WHERE contenttypeNodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.UserGroup2GranularPermission + " WHERE uniqueId IN (SELECT uniqueId FROM umbracoNode WHERE id = @id)",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.TagRelationship + " WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentChildType + " WHERE Id = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentChildType + " WHERE AllowedId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentTypeTree + " WHERE parentContentTypeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentTypeTree + " WHERE childContentTypeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE propertyTypeId IN (SELECT id FROM cmsPropertyType WHERE contentTypeId = @id)",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyType + " WHERE contentTypeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyTypeGroup + " WHERE contenttypeNodeId = @id",
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ internal class EntityContainerRepository : EntityRepositoryBase<int, EntityConta
|
||||
{
|
||||
NodeDto nodeDto = Database.FirstOrDefault<NodeDto>(Sql().SelectAll()
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(dto => dto.Text == name && dto.NodeObjectType == NodeObjectTypeId && dto.ParentId == parentId));
|
||||
.Where<NodeDto>(dto => dto.Text == name && dto.NodeObjectType == NodeObjectTypeId && dto.ParentId == parentId));
|
||||
|
||||
return nodeDto is not null;
|
||||
}
|
||||
|
||||
@@ -95,10 +95,6 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
ApplyOrdering(ref sql, ordering);
|
||||
}
|
||||
|
||||
// TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently
|
||||
// no matter what we always must have node id ordered at the end
|
||||
sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId");
|
||||
|
||||
// for content we must query for ContentEntityDto entities to produce the correct culture variant entity names
|
||||
var pageIndexToFetch = pageIndex + 1;
|
||||
IEnumerable<BaseDto> dtos;
|
||||
@@ -146,7 +142,15 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IEntitySlim> GetSiblings(Guid objectType, Guid targetKey, int before, int after, Ordering ordering)
|
||||
public IEnumerable<IEntitySlim> GetSiblings(
|
||||
ISet<Guid> objectTypes,
|
||||
Guid targetKey,
|
||||
int before,
|
||||
int after,
|
||||
IQuery<IUmbracoEntity>? filter,
|
||||
Ordering ordering,
|
||||
out long totalBefore,
|
||||
out long totalAfter)
|
||||
{
|
||||
// Ideally we don't want to have to do a second query for the parent ID, but the siblings query is already messy enough
|
||||
// without us also having to do a nested query for the parent ID too.
|
||||
@@ -159,13 +163,28 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
Sql<ISqlContext> orderingSql = Sql();
|
||||
ApplyOrdering(ref orderingSql, ordering);
|
||||
|
||||
// Get all children of the parent node which is not trashed, ordered by SortOrder, and assign each a row number.
|
||||
// Get all children of the parent node which are not trashed and match the provided object types.
|
||||
// Order by SortOrder, and assign each a row number.
|
||||
// These row numbers are important, we need them to select the "before" and "after" siblings of the target node.
|
||||
Sql<ISqlContext> rowNumberSql = Sql()
|
||||
.Select($"ROW_NUMBER() OVER ({orderingSql.SQL}) AS rn")
|
||||
.AndSelect<NodeDto>(n => n.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.ParentId == parentId && x.Trashed == false);
|
||||
.Where<NodeDto>(x => x.ParentId == parentId && x.Trashed == false)
|
||||
.WhereIn<NodeDto>(x => x.NodeObjectType, objectTypes);
|
||||
|
||||
// Apply the filter if provided.
|
||||
if (filter != null)
|
||||
{
|
||||
foreach (Tuple<string, object[]> filterClause in filter.GetWhereClauses())
|
||||
{
|
||||
rowNumberSql.Where(filterClause.Item1, filterClause.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
// By applying additional where clauses with parameters containing an unknown number of elements, the position of the parameters in
|
||||
// the final query for before and after positions will increase. So we need to calculate the offset based on the provided values.
|
||||
int beforeAfterParameterIndexOffset = GetBeforeAfterParameterOffset(objectTypes, filter);
|
||||
|
||||
// Find the specific row number of the target node.
|
||||
// We need this to determine the bounds of the row numbers to select.
|
||||
@@ -180,21 +199,66 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
IEnumerable<object> afterArguments = targetRowSql.Arguments.Concat([after]);
|
||||
|
||||
// Select the UniqueId of nodes which row number is within the specified range of the target node's row number.
|
||||
const int BeforeAfterParameterIndex = 3;
|
||||
var beforeAfterParameterIndex = BeforeAfterParameterIndex + beforeAfterParameterIndexOffset;
|
||||
var beforeArgumentsArray = beforeArguments.ToArray();
|
||||
var afterArgumentsArray = afterArguments.ToArray();
|
||||
Sql<ISqlContext>? mainSql = Sql()
|
||||
.Select("UniqueId")
|
||||
.From().AppendSubQuery(rowNumberSql, "NumberedNodes")
|
||||
.Where($"rn >= ({targetRowSql.SQL}) - @3", beforeArguments.ToArray())
|
||||
.Where($"rn <= ({targetRowSql.SQL}) + @3", afterArguments.ToArray())
|
||||
.Where($"rn >= ({targetRowSql.SQL}) - @{beforeAfterParameterIndex}", beforeArgumentsArray)
|
||||
.Where($"rn <= ({targetRowSql.SQL}) + @{beforeAfterParameterIndex}", afterArgumentsArray)
|
||||
.OrderBy("rn");
|
||||
|
||||
List<Guid>? keys = Database.Fetch<Guid>(mainSql);
|
||||
|
||||
totalBefore = GetNumberOfSiblingsOutsideSiblingRange(rowNumberSql, targetRowSql, beforeAfterParameterIndex, beforeArgumentsArray, true);
|
||||
totalAfter = GetNumberOfSiblingsOutsideSiblingRange(rowNumberSql, targetRowSql, beforeAfterParameterIndex, afterArgumentsArray, false);
|
||||
|
||||
if (keys is null || keys.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return PerformGetAll(objectType, ordering, sql => sql.WhereIn<NodeDto>(x => x.UniqueId, keys));
|
||||
// To re-use this method we need to provide a single object type. By convention for folder based trees, we provide the primary object type last.
|
||||
return PerformGetAll(objectTypes.ToArray(), ordering, sql => sql.WhereIn<NodeDto>(x => x.UniqueId, keys));
|
||||
}
|
||||
|
||||
private static int GetBeforeAfterParameterOffset(ISet<Guid> objectTypes, IQuery<IUmbracoEntity>? filter)
|
||||
{
|
||||
int beforeAfterParameterIndexOffset = 0;
|
||||
|
||||
// Increment for each object type.
|
||||
beforeAfterParameterIndexOffset += objectTypes.Count;
|
||||
|
||||
// Increment for the provided filter.
|
||||
if (filter != null)
|
||||
{
|
||||
foreach (Tuple<string, object[]> filterClause in filter.GetWhereClauses())
|
||||
{
|
||||
// We need to offset by one for each non-array parameter in the filter clause.
|
||||
// If a query is created using Contains or some other set based operation, we'll get both the array and the
|
||||
// items in the array provided in the where clauses. It's only the latter that count for applying parameters
|
||||
// to the SQL statement, and hence we should only offset by them.
|
||||
beforeAfterParameterIndexOffset += filterClause.Item2.Count(x => !x.GetType().IsArray);
|
||||
}
|
||||
}
|
||||
|
||||
return beforeAfterParameterIndexOffset;
|
||||
}
|
||||
|
||||
private long GetNumberOfSiblingsOutsideSiblingRange(
|
||||
Sql<ISqlContext> rowNumberSql,
|
||||
Sql<ISqlContext> targetRowSql,
|
||||
int parameterIndex,
|
||||
object[] arguments,
|
||||
bool getBefore)
|
||||
{
|
||||
Sql<ISqlContext>? sql = Sql()
|
||||
.SelectCount()
|
||||
.From().AppendSubQuery(rowNumberSql, "NumberedNodes")
|
||||
.Where($"rn {(getBefore ? "<" : ">")} ({targetRowSql.SQL}) {(getBefore ? "-" : "+")} @{parameterIndex}", arguments);
|
||||
return Database.ExecuteScalar<long>(sql);
|
||||
}
|
||||
|
||||
|
||||
@@ -270,16 +334,16 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
}
|
||||
|
||||
private IEnumerable<IEntitySlim> PerformGetAll(
|
||||
Guid objectType,
|
||||
Guid[] objectTypes,
|
||||
Ordering ordering,
|
||||
Action<Sql<ISqlContext>>? filter = null)
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document ||
|
||||
objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
var isMember = objectType == Constants.ObjectTypes.Member;
|
||||
var isContent = objectTypes.Contains(Constants.ObjectTypes.Document) ||
|
||||
objectTypes.Contains(Constants.ObjectTypes.DocumentBlueprint);
|
||||
var isMedia = objectTypes.Contains(Constants.ObjectTypes.Media);
|
||||
var isMember = objectTypes.Contains(Constants.ObjectTypes.Member);
|
||||
|
||||
Sql<ISqlContext> sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectType, ordering, filter);
|
||||
Sql<ISqlContext> sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypes, ordering, filter);
|
||||
return GetEntities(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
@@ -526,8 +590,17 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
Guid objectType,
|
||||
Ordering ordering,
|
||||
Action<Sql<ISqlContext>>? filter)
|
||||
=> GetFullSqlForEntityType(isContent, isMedia, isMember, [objectType], ordering, filter);
|
||||
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(
|
||||
bool isContent,
|
||||
bool isMedia,
|
||||
bool isMember,
|
||||
Guid[] objectTypes,
|
||||
Ordering ordering,
|
||||
Action<Sql<ISqlContext>>? filter)
|
||||
{
|
||||
Sql<ISqlContext> sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, new[] { objectType });
|
||||
Sql<ISqlContext> sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectTypes);
|
||||
AddGroupBy(isContent, isMedia, isMember, sql, false);
|
||||
ApplyOrdering(ref sql, ordering);
|
||||
|
||||
@@ -742,6 +815,8 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
|
||||
Ordering? runner = ordering;
|
||||
|
||||
Direction lastDirection = Direction.Ascending;
|
||||
bool orderingIncludesNodeId = false;
|
||||
do
|
||||
{
|
||||
|
||||
@@ -753,7 +828,10 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
case "PATH":
|
||||
orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path");
|
||||
break;
|
||||
|
||||
case "NODEID":
|
||||
orderBy = runner.OrderBy;
|
||||
orderingIncludesNodeId = true;
|
||||
break;
|
||||
default:
|
||||
orderBy = runner.OrderBy ?? string.Empty;
|
||||
break;
|
||||
@@ -768,11 +846,25 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend
|
||||
sql.OrderByDescending(orderBy);
|
||||
}
|
||||
|
||||
lastDirection = runner.Direction;
|
||||
|
||||
runner = runner.Next;
|
||||
}
|
||||
while (runner is not null);
|
||||
|
||||
|
||||
// If we haven't already included the node Id in the order by clause, order by node Id as well to ensure consistent results
|
||||
// when the provided sort yields entities with the same value.
|
||||
if (orderingIncludesNodeId is false)
|
||||
{
|
||||
if (lastDirection == Direction.Ascending)
|
||||
{
|
||||
sql.OrderBy<NodeDto>(x => x.NodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
sql.OrderByDescending<NodeDto>(x => x.NodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -72,6 +72,8 @@ public interface ISqlSyntaxProvider
|
||||
|
||||
string ConvertDecimalToOrderableString { get; }
|
||||
|
||||
string ConvertUniqueIdentifierToString => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default isolation level for the database
|
||||
/// </summary>
|
||||
|
||||
@@ -469,6 +469,8 @@ public abstract class SqlSyntaxProviderBase<TSyntax> : ISqlSyntaxProvider
|
||||
|
||||
public virtual string ConvertDecimalToOrderableString => "REPLACE(STR({0}, 20, 9), SPACE(1), '0')";
|
||||
|
||||
public virtual string ConvertUniqueIdentifierToString => "CONVERT(nvarchar(36), {0})";
|
||||
|
||||
private DbTypes InitColumnTypeMap()
|
||||
{
|
||||
var dbTypeMap = new DbTypesFactory();
|
||||
|
||||
@@ -31,6 +31,7 @@ public class RuntimeState : IRuntimeState
|
||||
private readonly IConflictingRouteService _conflictingRouteService = null!;
|
||||
private readonly IEnumerable<IDatabaseProviderMetadata> _databaseProviderMetadata = null!;
|
||||
private readonly IRuntimeModeValidationService _runtimeModeValidationService = null!;
|
||||
private readonly IDatabaseAvailabilityCheck _databaseAvailabilityCheck = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The initial <see cref="RuntimeState"/>
|
||||
@@ -46,6 +47,7 @@ public class RuntimeState : IRuntimeState
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RuntimeState" /> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public RuntimeState(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IOptions<UnattendedSettings> unattendedSettings,
|
||||
@@ -56,6 +58,34 @@ public class RuntimeState : IRuntimeState
|
||||
IConflictingRouteService conflictingRouteService,
|
||||
IEnumerable<IDatabaseProviderMetadata> databaseProviderMetadata,
|
||||
IRuntimeModeValidationService runtimeModeValidationService)
|
||||
: this(
|
||||
globalSettings,
|
||||
unattendedSettings,
|
||||
umbracoVersion,
|
||||
databaseFactory,
|
||||
logger,
|
||||
packageMigrationState,
|
||||
conflictingRouteService,
|
||||
databaseProviderMetadata,
|
||||
runtimeModeValidationService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDatabaseAvailabilityCheck>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RuntimeState" /> class.
|
||||
/// </summary>
|
||||
public RuntimeState(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IOptions<UnattendedSettings> unattendedSettings,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
ILogger<RuntimeState> logger,
|
||||
PendingPackageMigrations packageMigrationState,
|
||||
IConflictingRouteService conflictingRouteService,
|
||||
IEnumerable<IDatabaseProviderMetadata> databaseProviderMetadata,
|
||||
IRuntimeModeValidationService runtimeModeValidationService,
|
||||
IDatabaseAvailabilityCheck databaseAvailabilityCheck)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_unattendedSettings = unattendedSettings;
|
||||
@@ -66,6 +96,7 @@ public class RuntimeState : IRuntimeState
|
||||
_conflictingRouteService = conflictingRouteService;
|
||||
_databaseProviderMetadata = databaseProviderMetadata;
|
||||
_runtimeModeValidationService = runtimeModeValidationService;
|
||||
_databaseAvailabilityCheck = databaseAvailabilityCheck;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -242,7 +273,7 @@ public class RuntimeState : IRuntimeState
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!TryDbConnect(databaseFactory))
|
||||
if (_databaseAvailabilityCheck.IsDatabaseAvailable(databaseFactory) is false)
|
||||
{
|
||||
return UmbracoDatabaseState.CannotConnect;
|
||||
}
|
||||
@@ -305,27 +336,4 @@ public class RuntimeState : IRuntimeState
|
||||
}
|
||||
return CurrentMigrationState != FinalMigrationState;
|
||||
}
|
||||
|
||||
private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory)
|
||||
{
|
||||
// anything other than install wants a database - see if we can connect
|
||||
// (since this is an already existing database, assume localdb is ready)
|
||||
bool canConnect;
|
||||
var tries = 5;
|
||||
for (var i = 0; ;)
|
||||
{
|
||||
canConnect = databaseFactory.CanConnect;
|
||||
if (canConnect || ++i == tries)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
|
||||
{
|
||||
_logger.LogDebug("Could not immediately connect to database, trying again.");
|
||||
}
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
return canConnect;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Unicode;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Serialization;
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed class DefaultJsonSerializerEncoderFactory : IJsonSerializerEncoderFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public JavaScriptEncoder CreateEncoder<TSerializer>()
|
||||
where TSerializer : IJsonSerializer
|
||||
=> JavaScriptEncoder.Create(UnicodeRanges.BasicLatin);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
|
||||
@@ -14,11 +16,24 @@ public sealed class SystemTextConfigurationEditorJsonSerializer : SystemTextJson
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public SystemTextConfigurationEditorJsonSerializer()
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
|
||||
/// </summary>
|
||||
public SystemTextConfigurationEditorJsonSerializer(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
|
||||
: base(jsonSerializerEncoderFactory)
|
||||
=> _jsonSerializerOptions = new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
|
||||
Encoder = jsonSerializerEncoderFactory.CreateEncoder<SystemTextConfigurationEditorJsonSerializer>(),
|
||||
|
||||
// In some cases, configs aren't camel cased in the DB, so we have to resort to case insensitive
|
||||
// property name resolving when creating configuration objects (deserializing DB configs).
|
||||
PropertyNameCaseInsensitive = true,
|
||||
@@ -40,6 +55,7 @@ public sealed class SystemTextConfigurationEditorJsonSerializer : SystemTextJson
|
||||
.WithAddedModifier(UseAttributeConfiguredPropertyNames()),
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Serialization;
|
||||
|
||||
@@ -8,13 +11,27 @@ public sealed class SystemTextJsonSerializer : SystemTextJsonSerializerBase
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public SystemTextJsonSerializer()
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonSerializer" /> class.
|
||||
/// </summary>
|
||||
public SystemTextJsonSerializer()
|
||||
public SystemTextJsonSerializer(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
|
||||
: base(jsonSerializerEncoderFactory)
|
||||
=> _jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
|
||||
Encoder = jsonSerializerEncoderFactory.CreateEncoder<SystemTextJsonSerializer>(),
|
||||
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(),
|
||||
@@ -25,5 +42,6 @@ public sealed class SystemTextJsonSerializer : SystemTextJsonSerializerBase
|
||||
}
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -8,6 +11,28 @@ namespace Umbraco.Cms.Infrastructure.Serialization;
|
||||
|
||||
public abstract class SystemTextJsonSerializerBase : IJsonSerializer
|
||||
{
|
||||
private readonly IJsonSerializerEncoderFactory _jsonSerializerEncoderFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonSerializerBase" /> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
protected SystemTextJsonSerializerBase()
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonSerializerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonSerializerEncoderFactory">The <see cref="IJsonSerializerEncoderFactory"/> for creating the <see cref="JavaScriptEncoder"/>.</param>
|
||||
protected SystemTextJsonSerializerBase(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
|
||||
=> _jsonSerializerEncoderFactory = jsonSerializerEncoderFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Text.Json.JsonSerializerOptions"/>.
|
||||
/// </summary>
|
||||
protected abstract JsonSerializerOptions JsonSerializerOptions { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Serialization;
|
||||
@@ -9,23 +11,38 @@ public sealed class SystemTextWebhookJsonSerializer : SystemTextJsonSerializerBa
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public SystemTextWebhookJsonSerializer()
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextWebhookJsonSerializer" /> class.
|
||||
/// </summary>
|
||||
public SystemTextWebhookJsonSerializer()
|
||||
public SystemTextWebhookJsonSerializer(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
|
||||
: base(jsonSerializerEncoderFactory)
|
||||
=> _jsonSerializerOptions = new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
|
||||
Encoder = jsonSerializerEncoderFactory.CreateEncoder<SystemTextWebhookJsonSerializer>(),
|
||||
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(),
|
||||
new JsonUdiConverter(),
|
||||
new JsonUdiRangeConverter(),
|
||||
new JsonObjectConverter(), // Required for block editor values
|
||||
new JsonBlockValueConverter()
|
||||
new JsonBlockValueConverter(),
|
||||
},
|
||||
TypeInfoResolver = new WebhookJsonTypeResolver(),
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user