diff --git a/Directory.Build.props b/Directory.Build.props index b0b655f4b0..0364d4e086 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -30,8 +30,8 @@ false - true - 12.0.0 + false + 13.0.0 true true diff --git a/src/Umbraco.Cms.Api.Common/CompatibilitySuppressions.xml b/src/Umbraco.Cms.Api.Common/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.Api.Common/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.Api.Delivery/CompatibilitySuppressions.xml b/src/Umbraco.Cms.Api.Delivery/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.Api.Delivery/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/CompatibilitySuppressions.xml b/src/Umbraco.Cms.Imaging.ImageSharp/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.Imaging.ImageSharp/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.Imaging.ImageSharp2/CompatibilitySuppressions.xml b/src/Umbraco.Cms.Imaging.ImageSharp2/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.Imaging.ImageSharp2/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs index bac08556a3..823a9e737f 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Core; using Umbraco.Cms.Persistence.EFCore.Migrations; using Umbraco.Extensions; @@ -10,7 +11,7 @@ public class SqlServerMigrationProvider : IMigrationProvider public SqlServerMigrationProvider(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - public string ProviderName => "Microsoft.Data.SqlClient"; + public string ProviderName => Constants.ProviderNames.SQLServer; public async Task MigrateAsync(EFCoreMigration migration) { diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs index 6b161fc47f..2d561e9f5a 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs @@ -1,11 +1,12 @@ using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Core; using Umbraco.Cms.Persistence.EFCore.Migrations; namespace Umbraco.Cms.Persistence.EFCore.SqlServer; public class SqlServerMigrationProviderSetup : IMigrationProviderSetup { - public string ProviderName => "Microsoft.Data.SqlClient"; + public string ProviderName => Constants.ProviderNames.SQLServer; public void Setup(DbContextOptionsBuilder builder, string? connectionString) { 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 a75d681949..04e711f8d9 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 @@ -2,8 +2,6 @@ Umbraco CMS - Persistence - Entity Framework Core - SQL Server migrations Adds support for Entity Framework Core SQL Server migrations to Umbraco CMS. - - false diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs index 05d4024bb3..468f52021f 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Core; using Umbraco.Cms.Persistence.EFCore.Migrations; using Umbraco.Extensions; @@ -11,7 +12,7 @@ public class SqliteMigrationProvider : IMigrationProvider public SqliteMigrationProvider(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - public string ProviderName => "Microsoft.Data.Sqlite"; + public string ProviderName => Constants.ProviderNames.SQLLite; public async Task MigrateAsync(EFCoreMigration migration) { diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs index 4cba457768..3a1b97e76c 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs @@ -1,11 +1,12 @@ using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Core; using Umbraco.Cms.Persistence.EFCore.Migrations; namespace Umbraco.Cms.Persistence.EFCore.Sqlite; public class SqliteMigrationProviderSetup : IMigrationProviderSetup { - public string ProviderName => "Microsoft.Data.Sqlite"; + public string ProviderName => Constants.ProviderNames.SQLLite; public void Setup(DbContextOptionsBuilder builder, string? connectionString) { diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj index d6559a35ea..5cb9949c04 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj @@ -2,8 +2,6 @@ Umbraco CMS - Persistence - Entity Framework Core - SQLite migrations Adds support for Entity Framework Core SQLite migrations to Umbraco CMS. - - false diff --git a/src/Umbraco.Cms.Persistence.EFCore/CompatibilitySuppressions.xml b/src/Umbraco.Cms.Persistence.EFCore/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.Persistence.EFCore/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs b/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs index 8b8627df47..1b751c558b 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs @@ -20,7 +20,7 @@ public class UmbracoEFCoreComposer : IComposer builder.AddNotificationAsyncHandler(); builder.AddNotificationAsyncHandler(); - builder.Services.AddUmbracoEFCoreContext((options, connectionString, providerName) => + builder.Services.AddUmbracoDbContext((options) => { // Register the entity sets needed by OpenIddict. options.UseOpenIddict(); diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index ded5be40fd..d901088064 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -1,7 +1,9 @@ +using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; @@ -16,6 +18,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions { public delegate void DefaultEFCoreOptionsAction(DbContextOptionsBuilder options, string? providerName, string? connectionString); + [Obsolete("Use AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) instead.")] public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { @@ -24,7 +27,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions sp => { SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(),optionsBuilder.Options); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); }); services.AddPooledDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -38,6 +41,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions return services; } + [Obsolete("Use AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) instead.")] public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, string connectionString, string providerName, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { @@ -52,8 +56,8 @@ public static class UmbracoEFCoreServiceCollectionExtensions services.TryAddSingleton>( sp => { - SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(),optionsBuilder.Options); + defaultEFCoreOptionsAction?.Invoke(optionsBuilder, providerName, connectionString); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); }); services.AddPooledDbContextFactory(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -67,12 +71,117 @@ public static class UmbracoEFCoreServiceCollectionExtensions return services; } + /// + /// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes. + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) + where T : DbContext + { + return AddUmbracoDbContext(services, (IServiceProvider _, DbContextOptionsBuilder options) => + { + optionsAction?.Invoke(options); + }); + } + + /// + /// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes. + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) + where T : DbContext + { + optionsAction ??= (sp, options) => { }; + + var optionsBuilder = new DbContextOptionsBuilder(); + + services.TryAddSingleton>(sp => + { + optionsAction.Invoke(sp, optionsBuilder); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); + }); + services.AddPooledDbContextFactory(optionsAction); + services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); + + services.AddUnique, AmbientEFCoreScopeStack>(); + services.AddUnique, EFCoreScopeAccessor>(); + services.AddUnique, EFCoreScopeProvider>(); + services.AddSingleton>(); + services.AddSingleton>(); + + return services; + } + + /// + /// Sets the database provider. I.E UseSqlite or UseSqlServer based on the provider name. + /// + /// + /// + /// + /// + /// + /// Only supports the databases normally supported in Umbraco. + /// + public static void UseDatabaseProvider(this DbContextOptionsBuilder builder, string providerName, string connectionString) + { + switch (providerName) + { + case Constants.ProviderNames.SQLServer: + builder.UseSqlServer(connectionString); + break; + case Constants.ProviderNames.SQLLite: + builder.UseSqlite(connectionString); + break; + default: + throw new InvalidDataException($"The provider {providerName} is not supported. Manually add the add the UseXXX statement to the options. I.E UseNpgsql()"); + } + } + + /// + /// Sets the database provider to use based on the Umbraco connection string. + /// + /// + /// + public static void UseUmbracoDatabaseProvider(this DbContextOptionsBuilder builder, IServiceProvider serviceProvider) + { + ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; + + // Replace data directory + string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); + if (string.IsNullOrEmpty(dataDirectory) is false) + { + connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); + } + + if (string.IsNullOrEmpty(connectionStrings.ProviderName)) + { + Log.Warning("No database provider was set. ProviderName is null"); + return; + } + + if (string.IsNullOrEmpty(connectionStrings.ConnectionString)) + { + Log.Warning("No database provider was set. Connection string is null"); + return; + } + + builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString); + } + + [Obsolete] private static void SetupDbContext(DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder) { ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider); defaultEFCoreOptionsAction?.Invoke(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName); } + [Obsolete] private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider) { ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; diff --git a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs index 042cf2a52f..60e519de4c 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Persistence.EFCore.Migrations; @@ -77,7 +76,7 @@ public class UmbracoDbContext : DbContext foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes()) { - entity.SetTableName(Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName()); + entity.SetTableName(Core.Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName()); } } } diff --git a/src/Umbraco.Cms.Persistence.SqlServer/CompatibilitySuppressions.xml b/src/Umbraco.Cms.Persistence.SqlServer/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.Persistence.SqlServer/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.Persistence.Sqlite/CompatibilitySuppressions.xml b/src/Umbraco.Cms.Persistence.Sqlite/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.Persistence.Sqlite/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.StaticAssets/CompatibilitySuppressions.xml b/src/Umbraco.Cms.StaticAssets/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Cms.StaticAssets/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html index b3362fcda9..adda418723 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html @@ -21,5 +21,5 @@ -
-
\ No newline at end of file +
+
diff --git a/src/Umbraco.Core/CompatibilitySuppressions.xml b/src/Umbraco.Core/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Core/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-ProviderNames.cs b/src/Umbraco.Core/Constants-ProviderNames.cs new file mode 100644 index 0000000000..67f376612c --- /dev/null +++ b/src/Umbraco.Core/Constants-ProviderNames.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Cms.Core; + +public static partial class Constants +{ + public static class ProviderNames + { + public const string SQLLite = "Microsoft.Data.Sqlite"; + + public const string SQLServer = "Microsoft.Data.SqlClient"; + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 3ec37df7f2..cd009600ed 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -36,9 +36,9 @@ public static partial class UmbracoBuilderExtensions /// internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) { - builder.CacheRefreshers().Add(() => builder.TypeLoader.GetCacheRefreshers()); - builder.DataEditors().Add(() => builder.TypeLoader.GetDataEditors()); - builder.Actions().Add(() => builder .TypeLoader.GetActions()); + builder.CacheRefreshers().Add(builder.TypeLoader.GetCacheRefreshers); + builder.DataEditors().Add(builder.TypeLoader.GetDataEditors); + builder.Actions().Add(builder.TypeLoader.GetActions); // register known content apps builder.ContentApps() @@ -242,14 +242,14 @@ public static partial class UmbracoBuilderExtensions /// Gets the partial view snippets collection builder. /// /// The builder. - public static PartialViewSnippetCollectionBuilder? PartialViewSnippets(this IUmbracoBuilder builder) + public static PartialViewSnippetCollectionBuilder PartialViewSnippets(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// /// Gets the partial view macro snippets collection builder. /// /// The builder. - public static PartialViewMacroSnippetCollectionBuilder? PartialViewMacroSnippets(this IUmbracoBuilder builder) + public static PartialViewMacroSnippetCollectionBuilder PartialViewMacroSnippets(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs index 07ba917dc2..31c8025ddb 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependency.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs @@ -1,42 +1,64 @@ +using System.Text.Json.Serialization; + namespace Umbraco.Cms.Core.Deploy; /// -/// Represents an artifact dependency. +/// Represents an artifact dependency. /// /// -/// Dependencies have an order property which indicates whether it must be respected when ordering artifacts. -/// -/// Dependencies have a mode which can be Match or Exist depending on whether the checksum should -/// match. -/// +/// +/// Dependencies have an order property which indicates whether it must be respected when ordering artifacts. +/// +/// +/// Dependencies have a mode which can be or depending on whether the checksum should match. +/// /// public class ArtifactDependency { /// - /// Initializes a new instance of the ArtifactDependency class with an entity identifier and a mode. + /// Initializes a new instance of the class. /// - /// The entity identifier of the artifact that is a dependency. + /// The entity identifier of the artifact dependency. /// A value indicating whether the dependency is ordering. /// The dependency mode. - public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode) + /// The checksum. + public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode, string? checksum = null) { Udi = udi; Ordering = ordering; Mode = mode; + Checksum = checksum; } /// - /// Gets the entity id of the artifact that is a dependency. + /// Gets the entity identifier of the artifact dependency. /// + /// + /// The entity identifier of the artifact dependency. + /// public Udi Udi { get; } /// - /// Gets a value indicating whether the dependency is ordering. + /// Gets a value indicating whether the dependency is ordering. /// + /// + /// true if the dependency is ordering; otherwise, false. + /// public bool Ordering { get; } /// - /// Gets the dependency mode. + /// Gets the dependency mode. /// + /// + /// The dependency mode. + /// public ArtifactDependencyMode Mode { get; } + + /// + /// Gets the checksum. + /// + /// + /// The checksum. + /// + public string? Checksum { get; } } diff --git a/src/Umbraco.Core/Deploy/DataTypeConfigurationConnectorExtensions.cs b/src/Umbraco.Core/Deploy/DataTypeConfigurationConnectorExtensions.cs deleted file mode 100644 index dbd501d277..0000000000 --- a/src/Umbraco.Core/Deploy/DataTypeConfigurationConnectorExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Deploy; - -/// -/// Extension methods adding backwards-compatability between and . -/// -/// -/// These extension methods will be removed in Umbraco 13. -/// -public static class DataTypeConfigurationConnectorExtensions -{ - /// - /// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies. - /// - /// The connector. - /// The data type. - /// The dependencies. - /// The context cache. - /// - /// The artifact configuration value. - /// - /// - /// This extension method tries to make use of the on types also implementing . - /// - public static string? ToArtifact(this IDataTypeConfigurationConnector connector, IDataType dataType, ICollection dependencies, IContextCache contextCache) - => connector is IDataTypeConfigurationConnector2 connector2 - ? connector2.ToArtifact(dataType, dependencies, contextCache) - : connector.ToArtifact(dataType, dependencies); - - /// - /// Gets the data type configuration corresponding to an artifact configuration value. - /// - /// The connector. - /// The data type. - /// The artifact configuration value. - /// The context cache. - /// - /// The data type configuration. - /// - /// - /// This extension method tries to make use of the on types also implementing . - /// - public static object? FromArtifact(this IDataTypeConfigurationConnector connector, IDataType dataType, string? configuration, IContextCache contextCache) - => connector is IDataTypeConfigurationConnector2 connector2 - ? connector2.FromArtifact(dataType, configuration, contextCache) - : connector.FromArtifact(dataType, configuration); -} diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs index 36302efd07..4cb0690d1f 100644 --- a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs +++ b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs @@ -24,20 +24,20 @@ public interface IDataTypeConfigurationConnector /// /// The data type. /// The dependencies. + /// The context cache. /// /// The artifact configuration value. /// - [Obsolete($"Implement {nameof(IDataTypeConfigurationConnector2)} and use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - string? ToArtifact(IDataType dataType, ICollection dependencies); + string? ToArtifact(IDataType dataType, ICollection dependencies, IContextCache contextCache); /// /// Gets the data type configuration corresponding to an artifact configuration value. /// /// The data type. /// The artifact configuration value. + /// The context cache. /// /// The data type configuration. /// - [Obsolete($"Implement {nameof(IDataTypeConfigurationConnector2)} and use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - object? FromArtifact(IDataType dataType, string? configuration); + object? FromArtifact(IDataType dataType, string? configuration, IContextCache contextCache); } diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector2.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector2.cs deleted file mode 100644 index 772bc35dc4..0000000000 --- a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector2.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Deploy; - -/// -/// -/// This interface will be merged back into and removed in Umbraco 13. -/// -public interface IDataTypeConfigurationConnector2 : IDataTypeConfigurationConnector -{ - /// - /// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies. - /// - /// The data type. - /// The dependencies. - /// - /// The artifact configuration value. - /// - [Obsolete($"Use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - string? IDataTypeConfigurationConnector.ToArtifact(IDataType dataType, ICollection dependencies) - => ToArtifact(dataType, dependencies, PassThroughCache.Instance); - - /// - /// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies. - /// - /// The data type. - /// The dependencies. - /// The context cache. - /// - /// The artifact configuration value. - /// - string? ToArtifact(IDataType dataType, ICollection dependencies, IContextCache contextCache); - - /// - /// Gets the data type configuration corresponding to an artifact configuration value. - /// - /// The data type. - /// The artifact configuration value. - /// - /// The data type configuration. - /// - [Obsolete($"Use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - object? IDataTypeConfigurationConnector.FromArtifact(IDataType dataType, string? configuration) - => FromArtifact(dataType, configuration, PassThroughCache.Instance); - - /// - /// Gets the data type configuration corresponding to an artifact configuration value. - /// - /// The data type. - /// The artifact configuration value. - /// The context cache. - /// - /// The data type configuration. - /// - object? FromArtifact(IDataType dataType, string? configuration, IContextCache contextCache); -} diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index adf4c57502..84617943c6 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -12,21 +12,21 @@ public interface IServiceConnector : IDiscoverable /// Gets an artifact. /// /// The entity identifier of the artifact. + /// The context cache. /// /// The corresponding artifact, or null. /// - [Obsolete($"Implement {nameof(IServiceConnector2)} and use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - IArtifact? GetArtifact(Udi udi); + IArtifact? GetArtifact(Udi udi, IContextCache contextCache); /// /// Gets an artifact. /// /// The entity. + /// The context cache. /// /// The corresponding artifact. /// - [Obsolete($"Implement {nameof(IServiceConnector2)} and use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - IArtifact GetArtifact(object entity); + IArtifact GetArtifact(object entity, IContextCache contextCache); /// /// Initializes processing for an artifact. @@ -47,10 +47,10 @@ public interface IServiceConnector : IDiscoverable void Process(ArtifactDeployState dart, IDeployContext context, int pass); /// - /// Explodes a range into udis. + /// Explodes a range into UDIs. /// /// The range. - /// The list of udis where to add the new udis. + /// The list of UDIs where to add the new UDIs. /// /// Also, it's cool to have a method named Explode. Kaboom! /// @@ -78,9 +78,9 @@ public interface IServiceConnector : IDiscoverable /// /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now? /// - /// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do - /// not manage guids but only ints... so we have to provide a way to support it. The string id here - /// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to + /// At the moment our UI has a hard time returning proper UDIs, mainly because Core's tree do + /// not manage GUIDs but only integers... so we have to provide a way to support it. The string id here + /// can be either a real string (for string UDIs) or an "integer as a string", using the value "-1" to /// indicate the "root" i.e. an open udi. /// /// diff --git a/src/Umbraco.Core/Deploy/IServiceConnector2.cs b/src/Umbraco.Core/Deploy/IServiceConnector2.cs deleted file mode 100644 index 6c1558a956..0000000000 --- a/src/Umbraco.Core/Deploy/IServiceConnector2.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Umbraco.Cms.Core.Deploy; - -/// -/// -/// This interface will be merged back into and removed in Umbraco 13. -/// -public interface IServiceConnector2 : IServiceConnector -{ - /// - [Obsolete($"Use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - IArtifact? IServiceConnector.GetArtifact(Udi udi) - => GetArtifact(udi, PassThroughCache.Instance); - - /// - /// Gets an artifact. - /// - /// The entity identifier of the artifact. - /// The context cache. - /// - /// The corresponding artifact, or null. - /// - IArtifact? GetArtifact(Udi udi, IContextCache contextCache); - - /// - [Obsolete($"Use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - IArtifact IServiceConnector.GetArtifact(object entity) - => GetArtifact(entity, PassThroughCache.Instance); - - /// - /// Gets an artifact. - /// - /// The entity. - /// The context cache. - /// - /// The corresponding artifact. - /// - IArtifact GetArtifact(object entity, IContextCache contextCache); -} diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs index fe28e41017..5e5e2da1a4 100644 --- a/src/Umbraco.Core/Deploy/IValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -26,11 +26,11 @@ public interface IValueConnector /// The content property value. /// The value property type /// The content dependencies. + /// The context cache. /// /// The deploy property value. /// - [Obsolete($"Implement {nameof(IValueConnector2)} and use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies); + string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache); /// /// Gets the content property value corresponding to a deploy property value. @@ -38,9 +38,9 @@ public interface IValueConnector /// The deploy property value. /// The value property type /// The current content property value. + /// The context cache. /// /// The content property value. /// - [Obsolete($"Implement {nameof(IValueConnector2)} and use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue); + object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache); } diff --git a/src/Umbraco.Core/Deploy/IValueConnector2.cs b/src/Umbraco.Core/Deploy/IValueConnector2.cs deleted file mode 100644 index a0c99dca06..0000000000 --- a/src/Umbraco.Core/Deploy/IValueConnector2.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Deploy; - -/// -/// -/// This interface will be merged back into and removed in Umbraco 13. -/// -public interface IValueConnector2 : IValueConnector -{ - /// - [Obsolete($"Use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - string? IValueConnector.ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies) - => ToArtifact(value, propertyType, dependencies, PassThroughCache.Instance); - - /// - /// Gets the deploy property value corresponding to a content property value, and gather dependencies. - /// - /// The content property value. - /// The value property type - /// The content dependencies. - /// The context cache. - /// - /// The deploy property value. - /// - string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache); - - /// - [Obsolete($"Use the overload accepting {nameof(IContextCache)} instead. This overload will be removed in Umbraco 13.")] - object? IValueConnector.FromArtifact(string? value, IPropertyType propertyType, object? currentValue) - => FromArtifact(value, propertyType, currentValue, PassThroughCache.Instance); - - /// - /// Gets the content property value corresponding to a deploy property value. - /// - /// The deploy property value. - /// The value property type - /// The current content property value. - /// The context cache. - /// - /// The content property value. - /// - object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache); -} diff --git a/src/Umbraco.Core/Deploy/ServiceConnectorExtensions.cs b/src/Umbraco.Core/Deploy/ServiceConnectorExtensions.cs deleted file mode 100644 index 0d0000f97c..0000000000 --- a/src/Umbraco.Core/Deploy/ServiceConnectorExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Umbraco.Cms.Core.Deploy; - -/// -/// Extension methods adding backwards-compatability between and . -/// -/// -/// These extension methods will be removed in Umbraco 13. -/// -public static class ServiceConnectorExtensions -{ - /// - /// Gets an artifact. - /// - /// The connector. - /// The entity identifier of the artifact. - /// The context cache. - /// - /// The corresponding artifact, or null. - /// - /// - /// This extension method tries to make use of the on types also implementing . - /// - public static IArtifact? GetArtifact(this IServiceConnector connector, Udi udi, IContextCache contextCache) - => connector is IServiceConnector2 connector2 - ? connector2.GetArtifact(udi, contextCache) - : connector.GetArtifact(udi); - - /// - /// Gets an artifact. - /// - /// The connector. - /// The entity. - /// The context cache. - /// - /// The corresponding artifact. - /// - /// - /// This extension method tries to make use of the on types also implementing . - /// - public static IArtifact GetArtifact(this IServiceConnector connector, object entity, IContextCache contextCache) - => connector is IServiceConnector2 connector2 - ? connector2.GetArtifact(entity, contextCache) - : connector.GetArtifact(entity); -} diff --git a/src/Umbraco.Core/Deploy/ValueConnectorExtensions.cs b/src/Umbraco.Core/Deploy/ValueConnectorExtensions.cs deleted file mode 100644 index eadcee55e0..0000000000 --- a/src/Umbraco.Core/Deploy/ValueConnectorExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Deploy; - -/// -/// Extension methods adding backwards-compatability between and . -/// -/// -/// These extension methods will be removed in Umbraco 13. -/// -public static class ValueConnectorExtensions -{ - /// - /// Gets the artifact value corresponding to a property value and gather dependencies. - /// - /// The connector. - /// The property value. - /// The property type. - /// The dependencies. - /// The context cache. - /// - /// The artifact value. - /// - /// - /// This extension method tries to make use of the on types also implementing . - /// - public static string? ToArtifact(this IValueConnector connector, object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache) - => connector is IValueConnector2 connector2 - ? connector2.ToArtifact(value, propertyType, dependencies, contextCache) - : connector.ToArtifact(value, propertyType, dependencies); - - /// - /// Gets the property value corresponding to an artifact value. - /// - /// The connector. - /// The artifact value. - /// The property type. - /// The current property value. - /// The context cache. - /// - /// The property value. - /// - /// - /// This extension method tries to make use of the on types also implementing . - /// - public static object? FromArtifact(this IValueConnector connector, string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache) - => connector is IValueConnector2 connector2 - ? connector2.FromArtifact(value, propertyType, currentValue, contextCache) - : connector.FromArtifact(value, propertyType, currentValue); -} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs index 24d6f17eb0..c605d45a9a 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -8,60 +8,119 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase> items) : base(items) - { - } + { } // TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented // by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection - public IEnumerable GetAllReferences( - IPropertyCollection properties, - PropertyEditorCollection propertyEditors) + public ISet GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors) { - var trackedRelations = new HashSet(); + var references = new HashSet(); - foreach (IProperty p in properties) + foreach (IProperty property in properties) { - if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor? editor)) + if (!propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor)) { continue; } // TODO: We will need to change this once we support tracking via variants/segments // for now, we are tracking values from ALL variants - foreach (IPropertyValue propertyVal in p.Values) + foreach (IPropertyValue propertyValue in property.Values) { - var val = propertyVal.EditedValue; + object? value = propertyValue.EditedValue; - IDataValueEditor? valueEditor = editor?.GetValueEditor(); - if (valueEditor is IDataValueReference reference) + if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference) { - IEnumerable refs = reference.GetReferences(val); - foreach (UmbracoEntityReference r in refs) - { - trackedRelations.Add(r); - } + references.UnionWith(dataValueReference.GetReferences(value)); } // Loop over collection that may be add to existing property editors // implementation of GetReferences in IDataValueReference. // Allows developers to add support for references by a // package /property editor that did not implement IDataValueReference themselves - foreach (IDataValueReferenceFactory item in this) + foreach (IDataValueReferenceFactory dataValueReferenceFactory in this) { // Check if this value reference is for this datatype/editor // Then call it's GetReferences method - to see if the value stored - // in the dataeditor/property has referecnes to media/content items - if (item.IsForEditor(editor)) + // in the dataeditor/property has references to media/content items + if (dataValueReferenceFactory.IsForEditor(dataEditor)) { - foreach (UmbracoEntityReference r in item.GetDataValueReference().GetReferences(val)) - { - trackedRelations.Add(r); - } + references.UnionWith(dataValueReferenceFactory.GetDataValueReference().GetReferences(value)); } } } } - return trackedRelations; + return references; + } + + /// + /// Gets all relation type aliases that are automatically tracked. + /// + /// The property editors. + /// + /// All relation type aliases that are automatically tracked. + /// + public ISet GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) + { + // Always add default automatic relation types + var automaticRelationTypeAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes); + + // Add relation types for all property editors + foreach (IDataEditor dataEditor in propertyEditors) + { + automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor)); + } + + return automaticRelationTypeAliases; + } + + /// + /// Gets the relation type aliases that are automatically tracked for all properties. + /// + /// The properties. + /// The property editors. + /// + /// The relation type aliases that are automatically tracked for all properties. + /// + public ISet GetAutomaticRelationTypesAliases(IPropertyCollection properties, PropertyEditorCollection propertyEditors) + { + // Always add default automatic relation types + var automaticRelationTypeAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes); + + // Only add relation types that are used in the properties + foreach (IProperty property in properties) + { + if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor)) + { + automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor)); + } + } + + return automaticRelationTypeAliases; + } + + private IEnumerable GetAutomaticRelationTypesAliases(IDataEditor dataEditor) + { + if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference) + { + // Return custom relation types from value editor implementation + foreach (var alias in dataValueReference.GetAutomaticRelationTypesAliases()) + { + yield return alias; + } + } + + foreach (IDataValueReferenceFactory dataValueReferenceFactory in this) + { + if (dataValueReferenceFactory.IsForEditor(dataEditor)) + { + // Return custom relation types from factory + foreach (var alias in dataValueReferenceFactory.GetDataValueReference().GetAutomaticRelationTypesAliases()) + { + yield return alias; + } + } + } } } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index 5bd5ff23cc..8e661aa758 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -125,4 +125,8 @@ public interface IPublishedSnapshotService : IDisposable /// Cleans up unused snapshots /// Task CollectAsync(); + + void ResetLocalDb() + { + } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8cdb0d2b82..4f4dd097b1 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -16,18 +16,9 @@ - - - - - - - - - <_Parameter1>Umbraco.Tests diff --git a/src/Umbraco.Core/Webhooks/IWebhookEvent.cs b/src/Umbraco.Core/Webhooks/IWebhookEvent.cs index 85857c1aec..954055d104 100644 --- a/src/Umbraco.Core/Webhooks/IWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/IWebhookEvent.cs @@ -2,5 +2,5 @@ public interface IWebhookEvent { - string EventName { get; set; } + string EventName { get; } } diff --git a/src/Umbraco.Examine.Lucene/CompatibilitySuppressions.xml b/src/Umbraco.Examine.Lucene/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Examine.Lucene/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml b/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs index 609c5305dc..0dd42485a2 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs @@ -15,21 +15,21 @@ public static partial class UmbracoBuilderExtensions /// Gets the mappers collection builder. /// /// The builder. - public static MapperCollectionBuilder? Mappers(this IUmbracoBuilder builder) + public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// /// Gets the NPoco mappers collection builder. /// /// The builder. - public static NPocoMapperCollectionBuilder? NPocoMappers(this IUmbracoBuilder builder) + public static NPocoMapperCollectionBuilder NPocoMappers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// /// Gets the package migration plans collection builder. /// /// The builder. - public static PackageMigrationPlanCollectionBuilder? PackageMigrationPlans(this IUmbracoBuilder builder) + public static PackageMigrationPlanCollectionBuilder PackageMigrationPlans(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 373c4a2008..1dff6e7eeb 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -78,8 +78,8 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(factory => factory.GetRequiredService().SqlContext); - builder.NPocoMappers()?.Add(); - builder.PackageMigrationPlans()?.Add(() => builder.TypeLoader.GetPackageMigrationPlans()); + builder.NPocoMappers().Add(); + builder.PackageMigrationPlans().Add(builder.TypeLoader.GetPackageMigrationPlans()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -107,7 +107,7 @@ public static partial class UmbracoBuilderExtensions // register persistence mappers - required by database factory so needs to be done here // means the only place the collection can be modified is in a runtime - afterwards it // has been frozen and it is too late - builder.Mappers()?.AddCoreMappers(); + builder.Mappers().AddCoreMappers(); // register the scope provider builder.Services.AddSingleton(sp => ActivatorUtilities.CreateInstance(sp, sp.GetRequiredService())); // implements IScopeProvider, IScopeAccessor diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs index b55b4c4ca7..0e41ad89ca 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_0_0/ResetCache.cs @@ -1,22 +1,34 @@ -using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.PublishedCache; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_12_0_0; public class ResetCache : MigrationBase { private readonly IHostingEnvironment _hostingEnvironment; + private readonly IPublishedSnapshotService _publishedSnapshotService; + [Obsolete("Use ctor with all params - This will be removed in Umbraco 14.")] public ResetCache(IMigrationContext context, IHostingEnvironment hostingEnvironment) - : base(context) => + : this(context, hostingEnvironment, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public ResetCache(IMigrationContext context, IHostingEnvironment hostingEnvironment, IPublishedSnapshotService publishedSnapshotService) + : base(context) + { _hostingEnvironment = hostingEnvironment; + _publishedSnapshotService = publishedSnapshotService; + } protected override void Migrate() { RebuildCache = true; var distCacheFolderAbsolutePath = Path.Combine(_hostingEnvironment.LocalTempPath, "DistCache"); - var nuCacheFolderAbsolutePath = Path.Combine(_hostingEnvironment.LocalTempPath, "NuCache"); DeleteAllFilesInFolder(distCacheFolderAbsolutePath); - DeleteAllFilesInFolder(nuCacheFolderAbsolutePath); + _publishedSnapshotService.ResetLocalDb(); } private void DeleteAllFilesInFolder(string path) diff --git a/src/Umbraco.Infrastructure/Packaging/PackageMigrationPlanCollectionBuilder.cs b/src/Umbraco.Infrastructure/Packaging/PackageMigrationPlanCollectionBuilder.cs index 91b1364139..324257286e 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageMigrationPlanCollectionBuilder.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageMigrationPlanCollectionBuilder.cs @@ -2,7 +2,7 @@ using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Packaging; -public class PackageMigrationPlanCollectionBuilder : LazyCollectionBuilderBase { protected override PackageMigrationPlanCollectionBuilder This => this; diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs index 354083dfa8..0821232826 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs @@ -37,5 +37,5 @@ internal class AccessDto [ResultColumn] [Reference(ReferenceType.Many, ReferenceMemberName = "AccessId")] - public List Rules { get; set; } = null!; + public List Rules { get; set; } = new(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index de247a6120..fde1c6ca05 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1083,13 +1083,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected void PersistRelations(TEntity entity) { // Get all references from our core built in DataEditors/Property Editors - // Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection - var trackedRelations = new List(); - trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors)); - - var relationTypeAliases = GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToArray(); + // Along with seeing if developers want to collect additional references from the DataValueReferenceFactories collection + var trackedRelations = _dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors); // First delete all auto-relations for this entity + var relationTypeAliases = _dataValueReferenceFactories.GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToArray(); RelationRepository.DeleteByParent(entity.Id, relationTypeAliases); if (trackedRelations.Count == 0) @@ -1097,23 +1095,20 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return; } - trackedRelations = trackedRelations.Distinct().ToList(); - var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi) - .ToDictionary(x => (Udi)x!, x => x!.Guid); + var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi).WhereNotNull().ToDictionary(x => (Udi)x, x => x.Guid); // lookup in the DB all INT ids for the GUIDs and chuck into a dictionary - var keyToIds = Database.Fetch(Sql() + var keyToIds = Database.FetchByGroups(udiToGuids.Values, Constants.Sql.MaxParameterCount, guids => Sql() .Select(x => x.NodeId, x => x.UniqueId) .From() - .WhereIn(x => x.UniqueId, udiToGuids.Values)) + .WhereIn(x => x.UniqueId, guids)) .ToDictionary(x => x.UniqueId, x => x.NodeId); - var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty())? - .ToDictionary(x => x.Alias, x => x); + var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty()).ToDictionary(x => x.Alias, x => x); IEnumerable toSave = trackedRelations.Select(rel => { - if (allRelationTypes is null || !allRelationTypes.TryGetValue(rel.RelationTypeAlias, out IRelationType? relationType)) + if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out IRelationType? relationType)) { throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist"); } @@ -1135,31 +1130,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement RelationRepository.SaveBulk(toSave); } - private IEnumerable GetAutomaticRelationTypesAliases( - IPropertyCollection properties, - PropertyEditorCollection propertyEditors) - { - var automaticRelationTypesAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes); - - foreach (IProperty property in properties) - { - if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? editor) is false ) - { - continue; - } - - if (editor.GetValueEditor() is IDataValueReference reference) - { - foreach (var alias in reference.GetAutomaticRelationTypesAliases()) - { - automaticRelationTypesAliases.Add(alias); - } - } - } - - return automaticRelationTypesAliases; - } - /// /// Inserts property values for the content entity /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 3e92a4ae7f..59aa92bb82 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -1385,10 +1385,15 @@ AND umbracoNode.id <> @id", } // Now bulk update the umbracoDocument table - foreach (IGrouping> editValue in editedDocument.GroupBy(x => x.Value)) + // we need to do this in batches as the WhereIn Npoco method translates to all the nodeIds being passed in as parameters when using the SqlClient provider + // this results in to many parameters (>2100) being passed to the client when there are a lot of documents being normalized + foreach (IGrouping> groupByValue in editedDocument.GroupBy(x => x.Value)) { - Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key)) - .WhereIn(x => x.NodeId, editValue.Select(x => x.Key))); + foreach (IEnumerable> batch in groupByValue.InGroupsOf(2000)) + { + Database.Execute(Sql().Update(u => u.Set(x => x.Edited, groupByValue.Key)) + .WhereIn(x => x.NodeId, batch.Select(x => x.Key))); + } } } diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index 80d19e7a94..1c2e897ebc 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -37,9 +37,6 @@ - - - diff --git a/src/Umbraco.PublishedCache.NuCache/CompatibilitySuppressions.xml b/src/Umbraco.PublishedCache.NuCache/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.PublishedCache.NuCache/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index d921eb3f7c..6a9e1a982c 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -25,6 +25,7 @@ internal class Property : PublishedPropertyBase // the invariant-neutral source and inter values private readonly object? _sourceValue; private readonly ContentVariation _variations; + private bool _sourceValueIsInvariant; // the variant and non-variant object values private CacheValues? _cacheValues; @@ -89,6 +90,7 @@ internal class Property : PublishedPropertyBase // this variable is used for contextualizing the variation level when calculating property values. // it must be set to the union of variance (the combination of content type and property type variance). _variations = propertyType.Variations | content.ContentType.Variations; + _sourceValueIsInvariant = propertyType.Variations is ContentVariation.Nothing; } // clone for previewing as draft a published content that is published and has no draft @@ -104,6 +106,7 @@ internal class Property : PublishedPropertyBase _isMember = origin._isMember; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; _variations = origin._variations; + _sourceValueIsInvariant = origin._sourceValueIsInvariant; } // used to cache the CacheValues of this property @@ -152,7 +155,7 @@ internal class Property : PublishedPropertyBase { _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); - if (culture == string.Empty && segment == string.Empty) + if (_sourceValueIsInvariant || (culture == string.Empty && segment == string.Empty)) { return _sourceValue; } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 68b50cfd91..a7f8c42823 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -70,6 +70,8 @@ internal class PublishedSnapshotService : IPublishedSnapshotService private long _mediaGen; private ContentStore _mediaStore = null!; + private string LocalFilePath => Path.Combine(_hostingEnvironment.LocalTempPath, "NuCache"); + public PublishedSnapshotService( PublishedSnapshotServiceOptions options, ISyncBootStateAccessor syncBootStateAccessor, @@ -475,6 +477,22 @@ internal class PublishedSnapshotService : IPublishedSnapshotService return GetUid(_mediaStore, id); } + + public void ResetLocalDb() + { + _logger.LogInformation( + "Resetting NuCache local db"); + var path = LocalFilePath; + if (Directory.Exists(path) is false) + { + return; + } + + MainDomRelease(); + Directory.Delete(path, true); + MainDomRegister(); + } + /// /// Lazily populates the stores only when they are first requested /// @@ -603,7 +621,7 @@ internal class PublishedSnapshotService : IPublishedSnapshotService /// private void MainDomRegister() { - var path = GetLocalFilesPath(); + var path = GetAndEnsureLocalFilesPathExists(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); @@ -652,9 +670,9 @@ internal class PublishedSnapshotService : IPublishedSnapshotService } } - private string GetLocalFilesPath() + private string GetAndEnsureLocalFilesPathExists() { - var path = Path.Combine(_hostingEnvironment.LocalTempPath, "NuCache"); + var path = LocalFilePath; if (!Directory.Exists(path)) { diff --git a/src/Umbraco.Web.BackOffice/CompatibilitySuppressions.xml b/src/Umbraco.Web.BackOffice/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Web.BackOffice/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs index 5c9a96b71c..100d089451 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs @@ -188,7 +188,7 @@ internal sealed class ContentSaveValidationAttribute : TypeFilterAttribute break; case ContentSaveAction.Schedule: permissionToCheck.Add(ActionUpdate.ActionLetter); - permissionToCheck.Add(ActionToPublish.ActionLetter); + permissionToCheck.Add(ActionPublish.ActionLetter); contentToCheck = contentItem.PersistedContent; contentIdToCheck = contentToCheck?.Id ?? default; break; diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 12364baaf1..a64d0d2408 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Umbraco.Web.Common/CompatibilitySuppressions.xml b/src/Umbraco.Web.Common/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Web.Common/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 3099bc561c..1e455aea5f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -35,15 +35,18 @@ $scope.activeApp = null; //initializes any watches - function startWatches(content) { + var watchers = []; - $scope.$watchGroup(['culture', 'segment'], + function startWatches(content) { + clearWatchers(); + + watchers.push($scope.$watchGroup(['culture', 'segment'], function (value, oldValue) { createPreviewButton($scope.content, value[0], value[1]); - }); + })); //watch for changes to isNew, set the page.isNew accordingly and load the breadcrumb if we can - $scope.$watch('isNew', function (newVal, oldVal) { + watchers.push($scope.$watch('isNew', function (newVal, oldVal) { $scope.page.isNew = Object.toBoolean(newVal); @@ -59,8 +62,12 @@ }); } } - }); + })); + } + function clearWatchers () { + watchers.forEach(w => w()); + watchers = []; } //this initializes the editor with the data which will be called more than once if the data is re-loaded @@ -109,6 +116,7 @@ bindEvents(); resetVariantFlags(); + startWatches($scope.content); } function loadBreadcrumb() { @@ -241,7 +249,6 @@ appendRuntimeData(); init(); - startWatches($scope.content); syncTreeNode($scope.content, $scope.content.path, true); @@ -265,7 +272,6 @@ appendRuntimeData(); init(); - startWatches($scope.content); resetLastListPageNumber($scope.content); @@ -346,7 +352,10 @@ labelKey: "buttons_saveAndPreview" }; - const activeVariant = content.variants?.find((variant) => content.documentType?.variations === "Nothing" || variant.compositeId === compositeId); + let activeVariant = content.variants?.find((variant) => content.documentType?.variations === "Nothing" || variant.compositeId === compositeId); + /* if we can't find the active variant and there is only one variant available, we will use that. + this happens if we have a node that can vary by culture but there is only one language available. */ + activeVariant = !activeVariant && content.variants.length === 1 ? content.variants[0] : activeVariant; $scope.previewSubButtons = activeVariant?.additionalPreviewUrls?.map((additionalPreviewUrl) => { return { diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js index 2d09b521e3..4fc10dbfa3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js @@ -1,4 +1,4 @@ -// Filter to take a node id and grab it's name instead +// Filter to take a node id and grab it's name instead // Usage: {{ pickerAlias | ncNodeName }} // Cache for node names so we don't make a ton of requests @@ -78,6 +78,9 @@ angular.module("umbraco.filters").filter("ncNodeName", function (editorState, en }).filter("ncRichText", function () { return function (input) { - return $("
").html(input).text(); + // Get markup from RTE object or assume HTML + var html = input && Object.hasOwn(input, 'markup') ? input.markup : input; + + return $("
").html(html).text(); }; }); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 9c90527608..c7258061d5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -9,6 +9,8 @@ + + @@ -17,12 +19,6 @@ - - - all - - - true diff --git a/src/Umbraco.Web.Website/CompatibilitySuppressions.xml b/src/Umbraco.Web.Website/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/src/Umbraco.Web.Website/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index ec220ec3a8..1d532e6664 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -5,6 +5,7 @@ false + $(EnablePackageValidation) false diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 3884fec209..342622c094 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -7,8 +7,6 @@ - - diff --git a/tests/Umbraco.Tests.Common/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Common/CompatibilitySuppressions.xml deleted file mode 100644 index d864a9d227..0000000000 --- a/tests/Umbraco.Tests.Common/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - \ No newline at end of file diff --git a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index ae194a9f77..c84c8fad6e 100644 --- a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -5,7 +5,7 @@ Contains commonly used tools to write tests for Umbraco CMS, such as various builders for content etc. Umbraco.Cms.Tests.Common true - true + $(BaseEnablePackageValidation) diff --git a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml deleted file mode 100644 index 0abc4e0e3a..0000000000 --- a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net7.0 - - diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs new file mode 100644 index 0000000000..bfa3adb92b --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs @@ -0,0 +1,101 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Persistence.EFCore.DbContext; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] +public class CustomDbContextUmbracoProviderTests : UmbracoIntegrationTest +{ + [Test] + public void Can_Register_Custom_DbContext_And_Resolve() + { + var dbContext = Services.GetRequiredService(); + + Assert.IsNotNull(dbContext); + Assert.IsNotEmpty(dbContext.Database.GetConnectionString()); + } + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUmbracoDbContext((serviceProvider, options) => + { + options.UseUmbracoDatabaseProvider(serviceProvider); + }); + } + + internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext + { + public CustomDbContext(DbContextOptions options) + : base(options) + { + } + } +} + + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] +public class CustomDbContextCustomSqliteProviderTests : UmbracoIntegrationTest +{ + [Test] + public void Can_Register_Custom_DbContext_And_Resolve() + { + var dbContext = Services.GetRequiredService(); + + Assert.IsNotNull(dbContext); + Assert.IsNotEmpty(dbContext.Database.GetConnectionString()); + } + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUmbracoDbContext((serviceProvider, options) => + { + options.UseSqlite("Data Source=:memory:;Version=3;New=True;"); + }); + } + + internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext + { + public CustomDbContext(DbContextOptions options) + : base(options) + { + } + } +} + +[Obsolete] +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] +public class CustomDbContextLegacyExtensionProviderTests : UmbracoIntegrationTest +{ + [Test] + public void Can_Register_Custom_DbContext_And_Resolve() + { + var dbContext = Services.GetRequiredService(); + + Assert.IsNotNull(dbContext); + Assert.IsNotEmpty(dbContext.Database.GetConnectionString()); + } + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUmbracoEFCoreContext("Data Source=:memory:;Version=3;New=True;", "Microsoft.Data.Sqlite", (options, connectionString, providerName) => + { + options.UseSqlite(connectionString); + }); + } + + internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext + { + public CustomDbContext(DbContextOptions options) + : base(options) + { + } + } +} + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 74f8d64b4a..e706538a02 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -6,8 +6,7 @@ Contains helper classes for integration tests with Umbraco CMS, including all internal integration tests. Umbraco.Cms.Tests.Integration true - true - true + $(BaseEnablePackageValidation) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs index ccca8f9f3c..30a8d6a1fa 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs @@ -326,7 +326,7 @@ public class UdiTests } [UdiDefinition("foo", UdiType.GuidUdi)] - public class FooConnector : IServiceConnector2 + public class FooConnector : IServiceConnector { public IArtifact GetArtifact(Udi udi, IContextCache contextCache) => throw new NotImplementedException(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs index 6108f59e2e..cff072873f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; @@ -173,6 +171,40 @@ public class DataValueReferenceFactoryCollectionTests Assert.AreEqual(trackedUdi4, result.ElementAt(1).Udi.ToString()); } + [Test] + public void GetAutomaticRelationTypesAliases_ContainsDefault() + { + var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty)); + var properties = new PropertyCollection(); + + var resultA = collection.GetAutomaticRelationTypesAliases(propertyEditors).ToArray(); + var resultB = collection.GetAutomaticRelationTypesAliases(properties, propertyEditors).ToArray(); + + var expected = Constants.Conventions.RelationTypes.AutomaticRelationTypes; + CollectionAssert.AreEquivalent(expected, resultA, "Result A does not contain the expected relation type aliases."); + CollectionAssert.AreEquivalent(expected, resultB, "Result B does not contain the expected relation type aliases."); + } + + [Test] + public void GetAutomaticRelationTypesAliases_ContainsCustom() + { + var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield()); + + var labelPropertyEditor = new LabelPropertyEditor(DataValueEditorFactory, IOHelper, EditorConfigurationParser); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => labelPropertyEditor.Yield())); + var serializer = new ConfigurationEditorJsonSerializer(); + var property = new Property(new PropertyType(ShortStringHelper, new DataType(labelPropertyEditor, serializer))); + var properties = new PropertyCollection { property, property }; // Duplicate on purpose to test distinct aliases + + var resultA = collection.GetAutomaticRelationTypesAliases(propertyEditors).ToArray(); + var resultB = collection.GetAutomaticRelationTypesAliases(properties, propertyEditors).ToArray(); + + var expected = Constants.Conventions.RelationTypes.AutomaticRelationTypes.Append("umbTest"); + CollectionAssert.AreEquivalent(expected, resultA, "Result A does not contain the expected relation type aliases."); + CollectionAssert.AreEquivalent(expected, resultB, "Result B does not contain the expected relation type aliases."); + } + private class TestDataValueReferenceFactory : IDataValueReferenceFactory { public IDataValueReference GetDataValueReference() => new TestMediaDataValueReference(); @@ -196,6 +228,12 @@ public class DataValueReferenceFactoryCollectionTests yield return new UmbracoEntityReference(udi); } } + + public IEnumerable GetAutomaticRelationTypesAliases() => new[] + { + "umbTest", + "umbTest", // Duplicate on purpose to test distinct aliases + }; } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs new file mode 100644 index 0000000000..7d117b96c5 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs @@ -0,0 +1,152 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Published; + +[TestFixture] +public class PublishedContentVarianceTests +{ + private const string PropertyTypeAlias = "theProperty"; + private const string DaCulture = "da-DK"; + private const string EnCulture = "en-US"; + private const string Segment1 = "segment1"; + private const string Segment2 = "segment2"; + + [Test] + public void No_Content_Variation_Can_Get_Invariant_Property() + { + var content = CreatePublishedContent(ContentVariation.Nothing, ContentVariation.Nothing); + var value = GetPropertyValue(content); + Assert.AreEqual("Invariant property value", value); + } + + [TestCase(DaCulture)] + [TestCase(EnCulture)] + [TestCase("")] + public void Content_Culture_Variation_Can_Get_Invariant_Property(string culture) + { + var content = CreatePublishedContent(ContentVariation.Culture, ContentVariation.Nothing, variationContextCulture: culture); + var value = GetPropertyValue(content); + Assert.AreEqual("Invariant property value", value); + } + + [TestCase(Segment1)] + [TestCase(Segment2)] + [TestCase("")] + public void Content_Segment_Variation_Can_Get_Invariant_Property(string segment) + { + var content = CreatePublishedContent(ContentVariation.Culture, ContentVariation.Nothing, variationContextSegment: segment); + var value = GetPropertyValue(content); + Assert.AreEqual("Invariant property value", value); + } + + [TestCase(DaCulture, "DaDk property value")] + [TestCase(EnCulture, "EnUs property value")] + public void Content_Culture_Variation_Can_Get_Culture_Variant_Property(string culture, string expectedValue) + { + var content = CreatePublishedContent(ContentVariation.Culture, ContentVariation.Culture, variationContextCulture: culture); + var value = GetPropertyValue(content); + Assert.AreEqual(expectedValue, value); + } + + [TestCase(Segment1, "Segment1 property value")] + [TestCase(Segment2, "Segment2 property value")] + public void Content_Segment_Variation_Can_Get_Segment_Variant_Property(string segment, string expectedValue) + { + var content = CreatePublishedContent(ContentVariation.Segment, ContentVariation.Segment, variationContextSegment: segment); + var value = GetPropertyValue(content); + Assert.AreEqual(expectedValue, value); + } + + [TestCase(DaCulture, Segment1, "DaDk Segment1 property value")] + [TestCase(DaCulture, Segment2, "DaDk Segment2 property value")] + [TestCase(EnCulture, Segment1, "EnUs Segment1 property value")] + [TestCase(EnCulture, Segment2, "EnUs Segment2 property value")] + public void Content_Culture_And_Segment_Variation_Can_Get_Culture_And_Segment_Variant_Property(string culture, string segment, string expectedValue) + { + var content = CreatePublishedContent(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment, variationContextCulture: culture, variationContextSegment: segment); + var value = GetPropertyValue(content); + Assert.AreEqual(expectedValue, value); + } + + private object? GetPropertyValue(IPublishedContent content) => content.GetProperty(PropertyTypeAlias)!.GetValue(); + + private IPublishedContent CreatePublishedContent(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation, string? variationContextCulture = null, string? variationContextSegment = null) + { + var propertyType = new Mock(); + propertyType.SetupGet(p => p.Alias).Returns(PropertyTypeAlias); + propertyType.SetupGet(p => p.CacheLevel).Returns(PropertyCacheLevel.None); + propertyType.SetupGet(p => p.DeliveryApiCacheLevel).Returns(PropertyCacheLevel.None); + propertyType.SetupGet(p => p.Variations).Returns(propertyTypeVariation); + propertyType + .Setup(p => p.ConvertSourceToInter(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((IPublishedElement _, object? source, bool _) => source); + propertyType + .Setup(p => p.ConvertInterToObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((IPublishedElement _, PropertyCacheLevel _, object? inter, bool _) => inter); + + var contentType = new Mock(); + contentType.SetupGet(c => c.PropertyTypes).Returns(new[] { propertyType.Object }); + contentType.SetupGet(c => c.Variations).Returns(contentTypeVariation); + + var propertyData = new List(); + + switch (propertyTypeVariation) + { + case ContentVariation.Culture: + propertyData.Add(CreatePropertyData("EnUs property value", culture: EnCulture)); + propertyData.Add(CreatePropertyData("DaDk property value", culture: DaCulture)); + break; + case ContentVariation.Segment: + propertyData.Add(CreatePropertyData("Segment1 property value", segment: Segment1)); + propertyData.Add(CreatePropertyData("Segment2 property value", segment: Segment2)); + break; + case ContentVariation.CultureAndSegment: + propertyData.Add(CreatePropertyData("EnUs Segment1 property value", culture: EnCulture, segment: Segment1)); + propertyData.Add(CreatePropertyData("EnUs Segment2 property value", culture: EnCulture, segment: Segment2)); + propertyData.Add(CreatePropertyData("DaDk Segment1 property value", culture: DaCulture, segment: Segment1)); + propertyData.Add(CreatePropertyData("DaDk Segment2 property value", culture: DaCulture, segment: Segment2)); + break; + case ContentVariation.Nothing: + propertyData.Add(CreatePropertyData("Invariant property value")); + break; + } + + var properties = new Dictionary { { PropertyTypeAlias, propertyData.ToArray() } }; + + var contentNode = new ContentNode(123, Guid.NewGuid(), contentType.Object, 1, string.Empty, 1, 1, DateTime.Now, 1); + var contentData = new ContentData("bla", "bla", 1, DateTime.Now, 1, 1, true, properties, null); + + var elementCache = new FastDictionaryAppCache(); + var snapshotCache = new FastDictionaryAppCache(); + var publishedSnapshotMock = new Mock(); + publishedSnapshotMock.SetupGet(p => p.ElementsCache).Returns(elementCache); + publishedSnapshotMock.SetupGet(p => p.SnapshotCache).Returns(snapshotCache); + + var publishedSnapshot = publishedSnapshotMock.Object; + var publishedSnapshotAccessor = new Mock(); + publishedSnapshotAccessor.Setup(p => p.TryGetPublishedSnapshot(out publishedSnapshot)).Returns(true); + + var variationContextAccessorMock = new Mock(); + variationContextAccessorMock + .SetupGet(mock => mock.VariationContext) + .Returns(() => new VariationContext(variationContextCulture, variationContextSegment)); + + return new PublishedContent( + contentNode, + contentData, + publishedSnapshotAccessor.Object, + variationContextAccessorMock.Object, + Mock.Of()); + + PropertyData CreatePropertyData(string value, string? culture = null, string? segment = null) + => new() { Culture = culture ?? string.Empty, Segment = segment ?? string.Empty, Value = value }; + } +} diff --git a/umbraco.sln b/umbraco.sln index 696a51bf17..36d381c729 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -34,20 +34,20 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "ht StartServerOnDebug = "false" EndProjectSection EndProject -Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Login", "http://localhost:3961", "{A5A4D377-A873-4469-AB5F-24B32BC0E55A}" +Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Login", "http://localhost:3962", "{A5A4D377-A873-4469-AB5F-24B32BC0E55A}" ProjectSection(WebsiteProperties) = preProject UseIISExpress = "true" TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.8" - Debug.AspNetCompiler.VirtualPath = "/localhost_3961" + Debug.AspNetCompiler.VirtualPath = "/localhost_3962" Debug.AspNetCompiler.PhysicalPath = "src\Umbraco.Web.UI.Login\" - Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3961\" + Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3962\" Debug.AspNetCompiler.Updateable = "true" Debug.AspNetCompiler.ForceOverwrite = "true" Debug.AspNetCompiler.FixedNames = "false" Debug.AspNetCompiler.Debug = "True" - Release.AspNetCompiler.VirtualPath = "/localhost_3961" + Release.AspNetCompiler.VirtualPath = "/localhost_3962" Release.AspNetCompiler.PhysicalPath = "src\Umbraco.Web.UI.Login\" - Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3961\" + Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3962\" Release.AspNetCompiler.Updateable = "true" Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.FixedNames = "false" @@ -174,11 +174,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Api.Common", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Imaging.ImageSharp", "src\Umbraco.Cms.Imaging.ImageSharp\Umbraco.Cms.Imaging.ImageSharp.csproj", "{35E3DA10-5549-41DE-B7ED-CC29355BA9FD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Persistence.EFCore", "src\Umbraco.Cms.Persistence.EFCore\Umbraco.Cms.Persistence.EFCore.csproj", "{9046F56E-4AC3-4603-A6A3-3ACCF632997E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Persistence.EFCore", "src\Umbraco.Cms.Persistence.EFCore\Umbraco.Cms.Persistence.EFCore.csproj", "{9046F56E-4AC3-4603-A6A3-3ACCF632997E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Persistence.EFCore.Sqlite", "src\Umbraco.Cms.Persistence.EFCore.Sqlite\Umbraco.Cms.Persistence.EFCore.Sqlite.csproj", "{8B4771F0-8EC2-4761-BBCE-DDE073DB3B7A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Persistence.EFCore.Sqlite", "src\Umbraco.Cms.Persistence.EFCore.Sqlite\Umbraco.Cms.Persistence.EFCore.Sqlite.csproj", "{8B4771F0-8EC2-4761-BBCE-DDE073DB3B7A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Persistence.EFCore.SqlServer", "src\Umbraco.Cms.Persistence.EFCore.SqlServer\Umbraco.Cms.Persistence.EFCore.SqlServer.csproj", "{9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}" +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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -196,6 +196,9 @@ Global {3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU + {A5A4D377-A873-4469-AB5F-24B32BC0E55A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5A4D377-A873-4469-AB5F-24B32BC0E55A}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {A5A4D377-A873-4469-AB5F-24B32BC0E55A}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU @@ -326,18 +329,18 @@ Global {D48B5D6B-82FF-4235-986C-CDE646F41DEC}.Release|Any CPU.Build.0 = Release|Any CPU {D48B5D6B-82FF-4235-986C-CDE646F41DEC}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {D48B5D6B-82FF-4235-986C-CDE646F41DEC}.SkipTests|Any CPU.Build.0 = Debug|Any CPU - {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Release|Any CPU.Build.0 = Release|Any CPU - {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU - {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.SkipTests|Any CPU.Build.0 = Debug|Any CPU {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Release|Any CPU.Build.0 = Release|Any CPU {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.SkipTests|Any CPU.Build.0 = Debug|Any CPU + {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.Release|Any CPU.Build.0 = Release|Any CPU + {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU + {9046F56E-4AC3-4603-A6A3-3ACCF632997E}.SkipTests|Any CPU.Build.0 = Debug|Any CPU {8B4771F0-8EC2-4761-BBCE-DDE073DB3B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B4771F0-8EC2-4761-BBCE-DDE073DB3B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B4771F0-8EC2-4761-BBCE-DDE073DB3B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU