diff --git a/Directory.Packages.props b/Directory.Packages.props index fa6d14e980..23acaa35f2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,24 +12,24 @@ - - + + - - - - + + + + - + - - + + @@ -47,7 +47,7 @@ - + @@ -75,7 +75,7 @@ - + @@ -87,5 +87,7 @@ + + - \ No newline at end of file + diff --git a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj index 523d6f4982..10cc87c013 100644 --- a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj +++ b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj @@ -14,6 +14,9 @@ + + + diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj index 7e6fc6153d..d663ef0197 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj @@ -8,6 +8,9 @@ + + + diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj index 75e2a6fe60..9aef183cf6 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj @@ -8,6 +8,9 @@ + + + diff --git a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj index ff487ca66a..e186d9789d 100644 --- a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj +++ b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj @@ -35,9 +35,14 @@ - - - + + + + + + + + diff --git a/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj b/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj index b83a582ade..a9a7604e7c 100644 --- a/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj +++ b/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs index 6caa7593ab..92e3678e7d 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.Validators; /// /// A validator that validates that the value is not null or empty (if it is a string) /// -public sealed class RequiredValidator : IValueRequiredValidator, IValueValidator +public class RequiredValidator : IValueRequiredValidator, IValueValidator { [Obsolete($"Use the constructor that does not accept {nameof(ILocalizedTextService)}. Will be removed in V15.")] public RequiredValidator(ILocalizedTextService textService) @@ -24,7 +24,7 @@ public sealed class RequiredValidator : IValueRequiredValidator, IValueValidator ValidateRequired(value, valueType); /// - public IEnumerable ValidateRequired(object? value, string? valueType) + public virtual IEnumerable ValidateRequired(object? value, string? valueType) { if (value == null) { diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 6201691d9f..6d99898cd6 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -26,6 +26,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.PropertyEditors.ValueConverters; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Routing; @@ -237,6 +238,8 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + return builder; } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_3_0/AlignUpgradedDatabase.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_3_0/AlignUpgradedDatabase.cs index 84171e8717..6ee48ce0e7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_3_0/AlignUpgradedDatabase.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_3_0/AlignUpgradedDatabase.cs @@ -120,23 +120,48 @@ public class AlignUpgradedDatabase : MigrationBase // We need to do this to ensure we don't try to rename the constraint if it doesn't exist. const string tableName = "umbracoContentVersion"; const string columnName = "VersionDate"; + const string newColumnName = "versionDate"; + const string expectedConstraintName = "DF_umbracoContentVersion_versionDate"; + ColumnInfo? versionDateColumn = columns .FirstOrDefault(x => x is { TableName: tableName, ColumnName: columnName }); - if (versionDateColumn is null) + // we only want to rename the column if necessary + if (versionDateColumn is not null) { // The column was not found I.E. the column is correctly named - return; + RenameColumn(tableName, columnName, newColumnName, columns); } - RenameColumn(tableName, columnName, "versionDate", columns); - // Renames the default constraint for the column, // apparently the content version table used to be prefixed with cms and not umbraco // We don't have a fluid way to rename the default constraint so we have to use raw SQL // This should be okay though since we are only running this migration on SQL Server + Sql constraintNameQuery = Database.SqlContext.Sql(@$" +SELECT obj_Constraint.NAME AS 'constraintName' + FROM sys.objects obj_table + JOIN sys.objects obj_Constraint + ON obj_table.object_id = obj_Constraint.parent_object_id + JOIN sys.sysconstraints constraints + ON constraints.constid = obj_Constraint.object_id + JOIN sys.columns columns + ON columns.object_id = obj_table.object_id + AND columns.column_id = constraints.colid + WHERE obj_table.NAME = '{tableName}' + AND columns.NAME = '{newColumnName}' + AND obj_Constraint.type = 'D' +"); + var currentConstraintName = Database.ExecuteScalar(constraintNameQuery); + + + // only rename the constraint if necessary + if (currentConstraintName == expectedConstraintName) + { + return; + } + Sql renameConstraintQuery = Database.SqlContext.Sql( - "EXEC sp_rename N'DF_cmsContentVersion_VersionDate', N'DF_umbracoContentVersion_versionDate', N'OBJECT'"); + $"EXEC sp_rename N'{currentConstraintName}', N'{expectedConstraintName}', N'OBJECT'"); Database.Execute(renameConstraintQuery); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 025bdd887c..ef442b768f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Cache.PropertyEditors; @@ -10,6 +11,7 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -66,14 +68,17 @@ public class RichTextPropertyEditor : DataEditor internal class RichTextPropertyValueEditor : BlockValuePropertyValueEditorBase { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly ILocalizedTextService _localizedTextService; private readonly IHtmlSanitizer _htmlSanitizer; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IJsonSerializer _jsonSerializer; private readonly IBlockEditorElementTypeCache _elementTypeCache; + private readonly IRichTextRequiredValidator _richTextRequiredValidator; private readonly ILogger _logger; + [Obsolete("Use non-obsolete constructor. This is schedules for removal in v16.")] public RichTextPropertyValueEditor( DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, @@ -91,20 +96,64 @@ public class RichTextPropertyEditor : DataEditor IBlockEditorElementTypeCache elementTypeCache, IPropertyValidationService propertyValidationService, DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection) + : this( + attribute, + propertyEditors, + dataTypeReadCache, + logger, + backOfficeSecurityAccessor, + localizedTextService, + shortStringHelper, + imageSourceParser, + localLinkParser, + pastedImages, + jsonSerializer, + ioHelper, + htmlSanitizer, + elementTypeCache, + propertyValidationService, + dataValueReferenceFactoryCollection, + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + public RichTextPropertyValueEditor( + DataEditorAttribute attribute, + PropertyEditorCollection propertyEditors, + IDataTypeConfigurationCache dataTypeReadCache, + ILogger logger, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + IHtmlSanitizer htmlSanitizer, + IBlockEditorElementTypeCache elementTypeCache, + IPropertyValidationService propertyValidationService, + DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection, + IRichTextRequiredValidator richTextRequiredValidator) : base(attribute, propertyEditors, dataTypeReadCache, localizedTextService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactoryCollection) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _localizedTextService = localizedTextService; _imageSourceParser = imageSourceParser; _localLinkParser = localLinkParser; _pastedImages = pastedImages; _htmlSanitizer = htmlSanitizer; _elementTypeCache = elementTypeCache; + _richTextRequiredValidator = richTextRequiredValidator; _jsonSerializer = jsonSerializer; _logger = logger; Validators.Add(new RichTextEditorBlockValidator(propertyValidationService, CreateBlockEditorValues(), elementTypeCache, jsonSerializer, logger)); } + public override IValueRequiredValidator RequiredValidator => _richTextRequiredValidator; + /// public override object? ConfigurationObject { diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRequiredValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRequiredValidator.cs new file mode 100644 index 0000000000..7358e92f38 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRequiredValidator.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +internal interface IRichTextRequiredValidator : IValueRequiredValidator +{ +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRequiredValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRequiredValidator.cs new file mode 100644 index 0000000000..b239f0bda5 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRequiredValidator.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +internal class RichTextRequiredValidator : RequiredValidator, IRichTextRequiredValidator +{ + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + + public RichTextRequiredValidator(ILocalizedTextService textService, IJsonSerializer jsonSerializer, ILogger logger) : base(textService) + { + _jsonSerializer = jsonSerializer; + _logger = logger; + } + + public override IEnumerable ValidateRequired(object? value, string? valueType) => base.ValidateRequired(GetValue(value), valueType); + + private object? GetValue(object? value) + { + if(RichTextPropertyEditorHelper.TryParseRichTextEditorValue(value, _jsonSerializer, _logger, out RichTextEditorValue? richTextEditorValue)) + { + return richTextEditorValue?.Markup; + } + + return value; + } +} diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 4fa0721a68..ba92e96fc0 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -21,6 +21,8 @@ + + diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index a2fc54b77e..56091cad98 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit a2fc54b77e99de28a0669ab628ecfd7983df7ad8 +Subproject commit 56091cad981b6f51f73c4906ed674bd43c660d5d diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index e01362ca11..f5eef701cb 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -4,8 +4,8 @@ - - + + @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/umbraco.sln b/umbraco.sln index 59015768a8..5e26e18d8c 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -184,7 +184,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Persistence.EFC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Persistence.EFCore.SqlServer", "src\Umbraco.Cms.Persistence.EFCore.SqlServer\Umbraco.Cms.Persistence.EFCore.SqlServer.csproj", "{9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.AcceptanceTest.UmbracoProject", "tests\Umbraco.Tests.AcceptanceTest.UmbracoProject\Umbraco.Tests.AcceptanceTest.UmbracoProject.csproj", "{A13FF0A0-69FA-468A-9F79-565401D5C341}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Tests.AcceptanceTest.UmbracoProject", "tests\Umbraco.Tests.AcceptanceTest.UmbracoProject\Umbraco.Tests.AcceptanceTest.UmbracoProject.csproj", "{A13FF0A0-69FA-468A-9F79-565401D5C341}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Api.Management", "src\Umbraco.Cms.Api.Management\Umbraco.Cms.Api.Management.csproj", "{B4929148-3BD9-4589-829D-7C31FFCFF6D7}" EndProject @@ -272,11 +272,8 @@ Global {79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.SkipTests|Any CPU.Build.0 = Debug|Any CPU {2A5027D9-F71D-4957-929E-F7A56AA1B95A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A5027D9-F71D-4957-929E-F7A56AA1B95A}.Debug|Any CPU.Build.0 = Debug|Any CPU {2A5027D9-F71D-4957-929E-F7A56AA1B95A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A5027D9-F71D-4957-929E-F7A56AA1B95A}.Release|Any CPU.Build.0 = Release|Any CPU {2A5027D9-F71D-4957-929E-F7A56AA1B95A}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU - {2A5027D9-F71D-4957-929E-F7A56AA1B95A}.SkipTests|Any CPU.Build.0 = Debug|Any CPU {32F6A309-EC1E-4CDB-BA80-C804CF680BEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {32F6A309-EC1E-4CDB-BA80-C804CF680BEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {32F6A309-EC1E-4CDB-BA80-C804CF680BEE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -356,9 +353,7 @@ Global {9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}.SkipTests|Any CPU.Build.0 = Debug|Any CPU {A13FF0A0-69FA-468A-9F79-565401D5C341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A13FF0A0-69FA-468A-9F79-565401D5C341}.Debug|Any CPU.Build.0 = Debug|Any CPU {A13FF0A0-69FA-468A-9F79-565401D5C341}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A13FF0A0-69FA-468A-9F79-565401D5C341}.Release|Any CPU.Build.0 = Release|Any CPU {A13FF0A0-69FA-468A-9F79-565401D5C341}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {A13FF0A0-69FA-468A-9F79-565401D5C341}.SkipTests|Any CPU.Build.0 = Debug|Any CPU {B4929148-3BD9-4589-829D-7C31FFCFF6D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU