diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/AllPackageManifestController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/AllPackageManifestController.cs new file mode 100644 index 0000000000..d4b36fd9e9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/AllPackageManifestController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.Controllers.Package; + +public class AllPackageManifestController : PackageControllerBase +{ + private readonly IPackageManifestService _packageManifestService; + private readonly IUmbracoMapper _umbracoMapper; + + public AllPackageManifestController(IPackageManifestService packageManifestService, IUmbracoMapper umbracoMapper) + { + _packageManifestService = packageManifestService; + _umbracoMapper = umbracoMapper; + } + + // NOTE: this endpoint is deliberately created as non-paginated to ensure the fastest possible client initialization + [HttpGet("manifest")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task AllPackageManifests() + { + PackageManifest[] packageManifests = (await _packageManifestService.GetPackageManifestsAsync()).ToArray(); + return Ok(_umbracoMapper.MapEnumerable(packageManifests)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/JsonBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/JsonBuilderExtensions.cs index 270038dba3..3322b28978 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/JsonBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/JsonBuilderExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Api.Management.Serialization; using Umbraco.Cms.Api.Management.Services; namespace Umbraco.Cms.Api.Management.DependencyInjection; @@ -10,8 +9,7 @@ public static class JsonBuilderExtensions internal static IUmbracoBuilder AddJson(this IUmbracoBuilder builder) { builder.Services - .AddTransient() - .AddTransient(); + .AddTransient(); return builder; } diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs index 3fad1574f4..277cff945e 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs @@ -12,7 +12,10 @@ internal static class PackageBuilderExtensions { builder.Services.AddTransient(); - builder.WithCollectionBuilder().Add(); + builder + .WithCollectionBuilder() + .Add() + .Add(); return builder; } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Package/PackageManifestViewModelMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Package/PackageManifestViewModelMapDefinition.cs new file mode 100644 index 0000000000..e1ee64af74 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/Package/PackageManifestViewModelMapDefinition.cs @@ -0,0 +1,19 @@ +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.Mapping.Package; + +public class PackageManifestViewModelMapDefinition : IMapDefinition +{ + public void DefineMaps(IUmbracoMapper mapper) + => mapper.Define((_, _) => new PackageManifestViewModel(), Map); + + // Umbraco.Code.MapAll + private static void Map(PackageManifest source, PackageManifestViewModel target, MapperContext context) + { + target.Name = source.Name; + target.Version = source.Version; + target.Extensions = source.Extensions; + } +} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 52824318c7..e80894a676 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -3932,6 +3932,33 @@ } } }, + "/umbraco/management/api/v1/package/manifest": { + "get": { + "tags": [ + "Package" + ], + "operationId": "GetPackageManifest", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/PackageManifestModel" + } + ] + } + } + } + } + } + } + } + }, "/umbraco/management/api/v1/package/migration-status": { "get": { "tags": [ @@ -7708,6 +7735,23 @@ }, "additionalProperties": false }, + "PackageManifestModel": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string", + "nullable": true + }, + "extensions": { + "type": "array", + "items": { } + } + }, + "additionalProperties": false + }, "PackageMigrationStatusModel": { "type": "object", "properties": { diff --git a/src/Umbraco.Cms.Api.Management/Serialization/ISystemTextJsonSerializer.cs b/src/Umbraco.Cms.Api.Management/Serialization/ISystemTextJsonSerializer.cs deleted file mode 100644 index 8ec90b6740..0000000000 --- a/src/Umbraco.Cms.Api.Management/Serialization/ISystemTextJsonSerializer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Core.Serialization; - -namespace Umbraco.Cms.Api.Management.Serialization; - -public interface ISystemTextJsonSerializer : IJsonSerializer -{ -} diff --git a/src/Umbraco.Cms.Api.Management/Serialization/SystemTextJsonSerializer.cs b/src/Umbraco.Cms.Api.Management/Serialization/SystemTextJsonSerializer.cs deleted file mode 100644 index fce9df39cd..0000000000 --- a/src/Umbraco.Cms.Api.Management/Serialization/SystemTextJsonSerializer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json; - -namespace Umbraco.Cms.Api.Management.Serialization; - -public class SystemTextJsonSerializer : ISystemTextJsonSerializer -{ - private JsonSerializerOptions _jsonSerializerOptions; - public SystemTextJsonSerializer() - { - _jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - } - public string Serialize(object? input) => JsonSerializer.Serialize(input, _jsonSerializerOptions); - - public T? Deserialize(string input) => JsonSerializer.Deserialize(input, _jsonSerializerOptions); - - public T? DeserializeSubset(string input, string key) => throw new NotSupportedException(); -} diff --git a/src/Umbraco.Cms.Api.Management/Services/JsonPatchService.cs b/src/Umbraco.Cms.Api.Management/Services/JsonPatchService.cs index af10432492..850e4a2a17 100644 --- a/src/Umbraco.Cms.Api.Management/Services/JsonPatchService.cs +++ b/src/Umbraco.Cms.Api.Management/Services/JsonPatchService.cs @@ -2,21 +2,22 @@ using Json.Patch; using Umbraco.Cms.Api.Management.Serialization; using Umbraco.Cms.Api.Management.ViewModels.JsonPatch; +using Umbraco.Cms.Core.Serialization; namespace Umbraco.Cms.Api.Management.Services; public class JsonPatchService : IJsonPatchService { - private readonly ISystemTextJsonSerializer _systemTextJsonSerializer; + private readonly IJsonSerializer _jsonSerializer; - public JsonPatchService(ISystemTextJsonSerializer systemTextJsonSerializer) => _systemTextJsonSerializer = systemTextJsonSerializer; + public JsonPatchService(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer; public PatchResult? Patch(JsonPatchViewModel[] patchViewModel, object objectToPatch) { - var patchString = _systemTextJsonSerializer.Serialize(patchViewModel); + var patchString = _jsonSerializer.Serialize(patchViewModel); - var docString = _systemTextJsonSerializer.Serialize(objectToPatch); - JsonPatch? patch = _systemTextJsonSerializer.Deserialize(patchString); + var docString = _jsonSerializer.Serialize(objectToPatch); + JsonPatch? patch = _jsonSerializer.Deserialize(patchString); var doc = JsonNode.Parse(docString); return patch?.Apply(doc); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageManifestViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageManifestViewModel.cs new file mode 100644 index 0000000000..4b20d922bc --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageManifestViewModel.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Package; + +public class PackageManifestViewModel +{ + public string Name { get; set; } = string.Empty; + + public string? Version { get; set; } + + public object[] Extensions { get; set; } = Array.Empty(); +} diff --git a/src/Umbraco.Core/Cache/AppCacheExtensions.cs b/src/Umbraco.Core/Cache/AppCacheExtensions.cs index 0f1f242ed0..d70e293d4a 100644 --- a/src/Umbraco.Core/Cache/AppCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/AppCacheExtensions.cs @@ -61,4 +61,35 @@ public static class AppCacheExtensions return result.TryConvertTo().Result; } + + public static async Task GetCacheItemAsync( + this IAppPolicyCache provider, + string cacheKey, + Func> getCacheItemAsync, + TimeSpan? timeout, + bool isSliding = false, + string[]? dependentFiles = null) + { + var result = provider.Get(cacheKey); + + if (result == null) + { + result = await getCacheItemAsync(); + provider.Insert(cacheKey, () => result, timeout, isSliding, dependentFiles); + } + + return result == null ? default : result.TryConvertTo().Result; + } + + public static async Task InsertCacheItemAsync( + this IAppPolicyCache provider, + string cacheKey, + Func> getCacheItemAsync, + TimeSpan? timeout = null, + bool isSliding = false, + string[]? dependentFiles = null) + { + T value = await getCacheItemAsync(); + provider.Insert(cacheKey, () => value, timeout, isSliding, dependentFiles); + } } diff --git a/src/Umbraco.Core/CompatibilitySuppressions.xml b/src/Umbraco.Core/CompatibilitySuppressions.xml index e4376074a2..cd2cccc4a4 100644 --- a/src/Umbraco.Core/CompatibilitySuppressions.xml +++ b/src/Umbraco.Core/CompatibilitySuppressions.xml @@ -1,5 +1,278 @@  + + CP0001 + T:Umbraco.Cms.Core.Manifest.CompositePackageManifest + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.IManifestFilter + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.IManifestParser + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.IPackageManifest + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestAssets + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestContentAppDefinition + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestContentAppFactory + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestDashboard + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestFilterCollection + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestFilterCollectionBuilder + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestSection + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Configuration.Grid.GridConfig.#ctor(Umbraco.Cms.Core.Cache.AppCaches,Umbraco.Cms.Core.Manifest.IManifestParser,Umbraco.Cms.Core.Serialization.IJsonSerializer,Umbraco.Cms.Core.Hosting.IHostingEnvironment,Microsoft.Extensions.Logging.ILoggerFactory) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_AllowPackageTelemetry + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_BundleOptions + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_ContentApps + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_Dashboards + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_GridEditors + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_PackageName + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_PackageView + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_ParameterEditors + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_PropertyEditors + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_Scripts + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_Sections + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_Source + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.get_Stylesheets + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_AllowPackageTelemetry(System.Boolean) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_BundleOptions(Umbraco.Cms.Core.Manifest.BundleOptions) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_ContentApps(Umbraco.Cms.Core.Manifest.ManifestContentAppDefinition[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_Dashboards(Umbraco.Cms.Core.Manifest.ManifestDashboard[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_GridEditors(Umbraco.Cms.Core.PropertyEditors.GridEditor[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_PackageName(System.String) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_PackageView(System.String) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_ParameterEditors(Umbraco.Cms.Core.PropertyEditors.IDataEditor[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_PropertyEditors(Umbraco.Cms.Core.PropertyEditors.IDataEditor[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_Scripts(System.String[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_Sections(Umbraco.Cms.Core.Manifest.ManifestSection[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_Source(System.String) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Manifest.PackageManifest.set_Stylesheets(System.String[]) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Models.AuditItem.#ctor(System.Int32,Umbraco.Cms.Core.Models.AuditType,System.Int32,System.String,System.String,System.String) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0002 M:Umbraco.Cms.Core.Models.Blocks.BlockGridItem.get_ForceLeft @@ -357,6 +630,27 @@ lib/net7.0/Umbraco.Core.dll true + + CP0002 + M:Umbraco.Cms.Core.PropertyEditors.ParameterEditorCollection.#ctor(Umbraco.Cms.Core.PropertyEditors.DataEditorCollection,Umbraco.Cms.Core.Manifest.IManifestParser) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.PropertyEditors.PropertyEditorCollection.#ctor(Umbraco.Cms.Core.PropertyEditors.DataEditorCollection,Umbraco.Cms.Core.Manifest.IManifestParser) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0002 + M:Umbraco.Cms.Core.Services.Implement.AuditService.#ctor(Umbraco.Cms.Core.Scoping.ICoreScopeProvider,Microsoft.Extensions.Logging.ILoggerFactory,Umbraco.Cms.Core.Events.IEventMessagesFactory,Umbraco.Cms.Core.Persistence.Repositories.IAuditRepository,Umbraco.Cms.Core.Persistence.Repositories.IAuditEntryRepository) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0002 M:Umbraco.Cms.Core.Services.Implement.DataTypeService.#ctor(Umbraco.Cms.Core.PropertyEditors.IDataValueEditorFactory,Umbraco.Cms.Core.Scoping.ICoreScopeProvider,Microsoft.Extensions.Logging.ILoggerFactory,Umbraco.Cms.Core.Events.IEventMessagesFactory,Umbraco.Cms.Core.Persistence.Repositories.IDataTypeRepository,Umbraco.Cms.Core.Persistence.Repositories.IDataTypeContainerRepository,Umbraco.Cms.Core.Persistence.Repositories.IAuditRepository,Umbraco.Cms.Core.Persistence.Repositories.IEntityRepository,Umbraco.Cms.Core.Persistence.Repositories.IContentTypeRepository,Umbraco.Cms.Core.IO.IIOHelper,Umbraco.Cms.Core.Services.ILocalizedTextService,Umbraco.Cms.Core.Services.ILocalizationService,Umbraco.Cms.Core.Strings.IShortStringHelper,Umbraco.Cms.Core.Serialization.IJsonSerializer) @@ -427,6 +721,20 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Persistence.Repositories.ITrackedReferencesRepository.GetPagedDescendantsInReferences(System.Guid,System.Int64,System.Int64,System.Boolean,System.Int64@) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0006 + M:Umbraco.Cms.Core.Persistence.Repositories.ITrackedReferencesRepository.GetPagedItemsWithRelations(System.Collections.Generic.ISet{System.Guid},System.Int64,System.Int64,System.Boolean,System.Int64@) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 M:Umbraco.Cms.Core.PropertyEditors.IConfigurationEditor.FromConfigurationEditor(System.Collections.Generic.IDictionary{System.String,System.Object}) @@ -588,6 +896,20 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.IPackagingService.CreateCreatedPackageAsync(Umbraco.Cms.Core.Packaging.PackageDefinition,System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0006 + M:Umbraco.Cms.Core.Services.IPackagingService.DeleteCreatedPackageAsync(System.Guid,System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 M:Umbraco.Cms.Core.Services.IPackagingService.GetCreatedPackageByKey(System.Guid) @@ -595,6 +917,13 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.IPackagingService.GetCreatedPackageByKeyAsync(System.Guid) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 M:Umbraco.Cms.Core.Services.IPackagingService.GetInstalledPackagesFromMigrationPlans @@ -602,6 +931,48 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.IPackagingService.GetInstalledPackagesFromMigrationPlansAsync(System.Int32,System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0006 + M:Umbraco.Cms.Core.Services.IPackagingService.UpdateCreatedPackageAsync(Umbraco.Cms.Core.Packaging.PackageDefinition,System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0006 + M:Umbraco.Cms.Core.Services.ITrackedReferencesService.GetPagedDescendantsInReferencesAsync(System.Guid,System.Int64,System.Int64,System.Boolean) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0006 + M:Umbraco.Cms.Core.Services.ITrackedReferencesService.GetPagedItemsWithRelationsAsync(System.Collections.Generic.ISet{System.Guid},System.Int64,System.Int64,System.Boolean) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0006 + M:Umbraco.Cms.Core.Services.ITrackedReferencesService.GetPagedRelationsForItemAsync(System.Guid,System.Int64,System.Int64,System.Boolean) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + + + CP0006 + M:Umbraco.Cms.Core.Telemetry.ITelemetryService.GetTelemetryReportDataAsync + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 P:Umbraco.Cms.Core.Models.IDataType.ConfigurationData diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index be13776da8..a8ef00e9c0 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -13,24 +13,24 @@ public class GridConfig : IGridConfig { public GridConfig( AppCaches appCaches, - IManifestParser manifestParser, + ILegacyManifestParser legacyManifestParser, IJsonSerializer jsonSerializer, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, IGridEditorsConfigFileProviderFactory gridEditorsConfigFileProviderFactory) => EditorsConfig = - new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, loggerFactory.CreateLogger(), gridEditorsConfigFileProviderFactory); + new GridEditorsConfig(appCaches, hostingEnvironment, legacyManifestParser, jsonSerializer, loggerFactory.CreateLogger(), gridEditorsConfigFileProviderFactory); [Obsolete("Use other ctor - Will be removed in Umbraco 13")] public GridConfig( AppCaches appCaches, - IManifestParser manifestParser, + ILegacyManifestParser legacyManifestParser, IJsonSerializer jsonSerializer, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory) : this( appCaches, - manifestParser, + legacyManifestParser, jsonSerializer, hostingEnvironment, loggerFactory, diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index ab0ec8b182..64d471faf9 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -21,19 +21,19 @@ internal class GridEditorsConfig : IGridEditorsConfig private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; private readonly IGridEditorsConfigFileProviderFactory _gridEditorsConfigFileProviderFactory; - private readonly IManifestParser _manifestParser; + private readonly ILegacyManifestParser _legacyManifestParser; public GridEditorsConfig( AppCaches appCaches, IHostingEnvironment hostingEnvironment, - IManifestParser manifestParser, + ILegacyManifestParser legacyManifestParser, IJsonSerializer jsonSerializer, ILogger logger, IGridEditorsConfigFileProviderFactory gridEditorsConfigFileProviderFactory) { _appCaches = appCaches; _hostingEnvironment = hostingEnvironment; - _manifestParser = manifestParser; + _legacyManifestParser = legacyManifestParser; _jsonSerializer = jsonSerializer; _logger = logger; _gridEditorsConfigFileProviderFactory = gridEditorsConfigFileProviderFactory; @@ -43,13 +43,13 @@ internal class GridEditorsConfig : IGridEditorsConfig public GridEditorsConfig( AppCaches appCaches, IHostingEnvironment hostingEnvironment, - IManifestParser manifestParser, + ILegacyManifestParser legacyManifestParser, IJsonSerializer jsonSerializer, ILogger logger) : this( appCaches, hostingEnvironment, - manifestParser, + legacyManifestParser, jsonSerializer, logger, StaticServiceProvider.Instance.GetRequiredService()) @@ -120,7 +120,7 @@ internal class GridEditorsConfig : IGridEditorsConfig } // Add manifest editors, skip duplicates - foreach (GridEditor gridEditor in _manifestParser.CombinedManifest.GridEditors) + foreach (GridEditor gridEditor in _legacyManifestParser.CombinedManifest.GridEditors) { if (editors.Contains(gridEditor) == false) { diff --git a/src/Umbraco.Core/Configuration/Models/PackageManifestSettings.cs b/src/Umbraco.Core/Configuration/Models/PackageManifestSettings.cs new file mode 100644 index 0000000000..f353281ddd --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/PackageManifestSettings.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for package manifest settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigPackageManifests)] +public class PackageManifestSettings +{ + public TimeSpan CacheTimeout { get; set; } = TimeSpan.FromMinutes(10); +} diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index 656bcd1cdf..782ead11b9 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -62,6 +62,7 @@ public static partial class Constants public const string ConfigHelpPage = ConfigPrefix + "HelpPage"; public const string ConfigInstallDefaultData = ConfigPrefix + "InstallDefaultData"; public const string ConfigDataTypes = ConfigPrefix + "DataTypes"; + public const string ConfigPackageManifests = ConfigPrefix + "PackageManifests"; public static class NamedOptions { diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs index fe6fdd423a..002fe15628 100644 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs @@ -27,10 +27,10 @@ public class ContentAppFactoryCollectionBuilder : OrderedCollectionBuilderBase(); + ILegacyManifestParser legacyManifestParser = factory.GetRequiredService(); IIOHelper ioHelper = factory.GetRequiredService(); return base.CreateItems(factory) - .Concat(manifestParser.CombinedManifest.ContentApps.Select(x => - new ManifestContentAppFactory(x, ioHelper))); + .Concat(legacyManifestParser.CombinedManifest.ContentApps.Select(x => + new LegacyManifestContentAppFactory(x, ioHelper))); } } diff --git a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs index 50867c90f4..3e3fed260f 100644 --- a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs +++ b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs @@ -13,17 +13,17 @@ public class DashboardCollectionBuilder : WeightedCollectionBuilderBase(); + ILegacyManifestParser legacyManifestParser = factory.GetRequiredService(); IEnumerable dashboardSections = - Merge(base.CreateItems(factory), manifestParser.CombinedManifest.Dashboards); + Merge(base.CreateItems(factory), legacyManifestParser.CombinedManifest.Dashboards); return dashboardSections; } private IEnumerable Merge( IEnumerable dashboardsFromCode, - IReadOnlyList dashboardFromManifest) => + IReadOnlyList dashboardFromManifest) => dashboardsFromCode.Concat(dashboardFromManifest) .Where(x => !string.IsNullOrEmpty(x.Alias)) .OrderBy(GetWeight); @@ -32,7 +32,7 @@ public class DashboardCollectionBuilder : WeightedCollectionBuilderBaseThe type of the manifest filter. /// The Builder. public static IUmbracoBuilder AddManifestFilter(this IUmbracoBuilder builder) - where T : class, IManifestFilter + where T : class, ILegacyManifestFilter { builder.ManifestFilters().Append(); return builder; diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 6f735a003c..93ac06f4f8 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -83,7 +83,7 @@ public static partial class UmbracoBuilderExtensions .Add() .Add() .Add() - .Add() + .Add() .Add() .Add() .Add() @@ -270,8 +270,8 @@ public static partial class UmbracoBuilderExtensions /// Gets the manifest filter collection builder. /// /// The builder. - public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + public static LegacyManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); /// /// Gets the content finders collection builder. diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 92e10c7b1c..a35b38a509 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -85,7 +85,8 @@ public static partial class UmbracoBuilderExtensions .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() - .AddUmbracoOptions(); + .AddUmbracoOptions() + .AddUmbracoOptions(); // Configure connection string and ensure it's updated when the configuration changes builder.Services.AddSingleton, ConfigureConnectionStrings>(); diff --git a/src/Umbraco.Core/IO/IManifestFileProviderFactory.cs b/src/Umbraco.Core/IO/ILegacyPackageManifestFileProviderFactory.cs similarity index 72% rename from src/Umbraco.Core/IO/IManifestFileProviderFactory.cs rename to src/Umbraco.Core/IO/ILegacyPackageManifestFileProviderFactory.cs index 982b029c27..39414a87d5 100644 --- a/src/Umbraco.Core/IO/IManifestFileProviderFactory.cs +++ b/src/Umbraco.Core/IO/ILegacyPackageManifestFileProviderFactory.cs @@ -5,6 +5,6 @@ namespace Umbraco.Cms.Core.IO; /// /// Factory for creating instances for providing the package.manifest file. /// -public interface IManifestFileProviderFactory : IFileProviderFactory +public interface ILegacyPackageManifestFileProviderFactory : IFileProviderFactory { } diff --git a/src/Umbraco.Core/IO/IPackageManifestFileProviderFactory.cs b/src/Umbraco.Core/IO/IPackageManifestFileProviderFactory.cs new file mode 100644 index 0000000000..39afc36d2f --- /dev/null +++ b/src/Umbraco.Core/IO/IPackageManifestFileProviderFactory.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Cms.Core.IO; + +/// +/// Factory for creating instances for providing the umbraco-package.json file. +/// +public interface IPackageManifestFileProviderFactory : IFileProviderFactory +{ +} diff --git a/src/Umbraco.Core/Manifest/CompositePackageManifest.cs b/src/Umbraco.Core/Manifest/CompositeLegacyPackageManifest.cs similarity index 68% rename from src/Umbraco.Core/Manifest/CompositePackageManifest.cs rename to src/Umbraco.Core/Manifest/CompositeLegacyPackageManifest.cs index 5e41681ea6..d0512a1753 100644 --- a/src/Umbraco.Core/Manifest/CompositePackageManifest.cs +++ b/src/Umbraco.Core/Manifest/CompositeLegacyPackageManifest.cs @@ -5,17 +5,17 @@ namespace Umbraco.Cms.Core.Manifest; /// /// A package manifest made up of all combined manifests /// -public class CompositePackageManifest +public class CompositeLegacyPackageManifest { - public CompositePackageManifest( + public CompositeLegacyPackageManifest( IReadOnlyList propertyEditors, IReadOnlyList parameterEditors, IReadOnlyList gridEditors, - IReadOnlyList contentApps, - IReadOnlyList dashboards, - IReadOnlyList sections, - IReadOnlyDictionary> scripts, - IReadOnlyDictionary> stylesheets) + IReadOnlyList contentApps, + IReadOnlyList dashboards, + IReadOnlyList sections, + IReadOnlyDictionary> scripts, + IReadOnlyDictionary> stylesheets) { PropertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); ParameterEditors = parameterEditors ?? throw new ArgumentNullException(nameof(parameterEditors)); @@ -45,19 +45,19 @@ public class CompositePackageManifest /// /// Gets or sets the content apps listed in the manifest. /// - public IReadOnlyList ContentApps { get; } + public IReadOnlyList ContentApps { get; } /// /// Gets or sets the dashboards listed in the manifest. /// - public IReadOnlyList Dashboards { get; } + public IReadOnlyList Dashboards { get; } /// /// Gets or sets the sections listed in the manifest. /// - public IReadOnlyCollection Sections { get; } + public IReadOnlyCollection Sections { get; } - public IReadOnlyDictionary> Scripts { get; } + public IReadOnlyDictionary> Scripts { get; } - public IReadOnlyDictionary> Stylesheets { get; } + public IReadOnlyDictionary> Stylesheets { get; } } diff --git a/src/Umbraco.Core/Manifest/IManifestFilter.cs b/src/Umbraco.Core/Manifest/ILegacyManifestFilter.cs similarity index 79% rename from src/Umbraco.Core/Manifest/IManifestFilter.cs rename to src/Umbraco.Core/Manifest/ILegacyManifestFilter.cs index d2998a0839..fd72466cbc 100644 --- a/src/Umbraco.Core/Manifest/IManifestFilter.cs +++ b/src/Umbraco.Core/Manifest/ILegacyManifestFilter.cs @@ -3,7 +3,7 @@ namespace Umbraco.Cms.Core.Manifest; /// /// Provides filtering for package manifests. /// -public interface IManifestFilter +public interface ILegacyManifestFilter { /// /// Filters package manifests. @@ -12,5 +12,5 @@ public interface IManifestFilter /// /// It is possible to remove, change, or add manifests. /// - void Filter(List manifests); + void Filter(List manifests); } diff --git a/src/Umbraco.Core/Manifest/IManifestParser.cs b/src/Umbraco.Core/Manifest/ILegacyManifestParser.cs similarity index 66% rename from src/Umbraco.Core/Manifest/IManifestParser.cs rename to src/Umbraco.Core/Manifest/ILegacyManifestParser.cs index f8b29e9f56..0a854774b3 100644 --- a/src/Umbraco.Core/Manifest/IManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ILegacyManifestParser.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Core.Manifest; -public interface IManifestParser +public interface ILegacyManifestParser { string AppPluginsPath { get; set; } @@ -8,16 +8,16 @@ public interface IManifestParser /// Gets all manifests, merged into a single manifest object. /// /// - CompositePackageManifest CombinedManifest { get; } + CompositeLegacyPackageManifest CombinedManifest { get; } /// /// Parses a manifest. /// - PackageManifest ParseManifest(string text); + LegacyPackageManifest ParseManifest(string text); /// /// Returns all package individual manifests /// /// - IEnumerable GetManifests(); + IEnumerable GetManifests(); } diff --git a/src/Umbraco.Core/Manifest/IPackageManifest.cs b/src/Umbraco.Core/Manifest/ILegacyPackageManifest.cs similarity index 89% rename from src/Umbraco.Core/Manifest/IPackageManifest.cs rename to src/Umbraco.Core/Manifest/ILegacyPackageManifest.cs index ba911b183c..21e1903e18 100644 --- a/src/Umbraco.Core/Manifest/IPackageManifest.cs +++ b/src/Umbraco.Core/Manifest/ILegacyPackageManifest.cs @@ -3,7 +3,7 @@ using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Manifest; -public interface IPackageManifest +public interface ILegacyPackageManifest { /// /// Gets the source path of the manifest. @@ -50,17 +50,17 @@ public interface IPackageManifest /// Gets or sets the content apps listed in the manifest. /// [DataMember(Name = "contentApps")] - ManifestContentAppDefinition[] ContentApps { get; set; } + LegacyManifestContentAppDefinition[] ContentApps { get; set; } /// /// Gets or sets the dashboards listed in the manifest. /// [DataMember(Name = "dashboards")] - ManifestDashboard[] Dashboards { get; set; } + LegacyManifestDashboard[] Dashboards { get; set; } /// /// Gets or sets the sections listed in the manifest. /// [DataMember(Name = "sections")] - ManifestSection[] Sections { get; set; } + LegacyManifestSection[] Sections { get; set; } } diff --git a/src/Umbraco.Core/Manifest/IPackageManifestService.cs b/src/Umbraco.Core/Manifest/IPackageManifestService.cs new file mode 100644 index 0000000000..6588861411 --- /dev/null +++ b/src/Umbraco.Core/Manifest/IPackageManifestService.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Core.Manifest; + +public interface IPackageManifestService +{ + Task> GetPackageManifestsAsync(); +} diff --git a/src/Umbraco.Core/Manifest/ManifestAssets.cs b/src/Umbraco.Core/Manifest/LegacyManifestAssets.cs similarity index 72% rename from src/Umbraco.Core/Manifest/ManifestAssets.cs rename to src/Umbraco.Core/Manifest/LegacyManifestAssets.cs index 2bd84a1bdd..05b0123a0d 100644 --- a/src/Umbraco.Core/Manifest/ManifestAssets.cs +++ b/src/Umbraco.Core/Manifest/LegacyManifestAssets.cs @@ -1,8 +1,8 @@ namespace Umbraco.Cms.Core.Manifest; -public class ManifestAssets +public class LegacyManifestAssets { - public ManifestAssets(string? packageName, IReadOnlyList assets) + public LegacyManifestAssets(string? packageName, IReadOnlyList assets) { PackageName = packageName ?? throw new ArgumentNullException(nameof(packageName)); Assets = assets ?? throw new ArgumentNullException(nameof(assets)); diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/LegacyManifestContentAppDefinition.cs similarity index 93% rename from src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs rename to src/Umbraco.Core/Manifest/LegacyManifestContentAppDefinition.cs index 5bfc2a740e..914923ec31 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/LegacyManifestContentAppDefinition.cs @@ -22,9 +22,9 @@ namespace Umbraco.Cms.Core.Manifest; /// /// Represents a content app definition, parsed from a manifest. /// -/// Is used to create an actual . +/// Is used to create an actual . [DataContract(Name = "appdef", Namespace = "")] -public class ManifestContentAppDefinition +public class LegacyManifestContentAppDefinition { private readonly string? _view; diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/LegacyManifestContentAppFactory.cs similarity index 96% rename from src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs rename to src/Umbraco.Core/Manifest/LegacyManifestContentAppFactory.cs index 122ecc1cb7..56795f7e59 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/LegacyManifestContentAppFactory.cs @@ -29,15 +29,15 @@ namespace Umbraco.Cms.Core.Manifest; /// /// Represents a content app factory, for content apps parsed from the manifest. /// -public class ManifestContentAppFactory : IContentAppFactory +public class LegacyManifestContentAppFactory : IContentAppFactory { - private readonly ManifestContentAppDefinition _definition; + private readonly LegacyManifestContentAppDefinition _definition; private readonly IIOHelper _ioHelper; private ContentApp? _app; private ShowRule[]? _showRules; - public ManifestContentAppFactory(ManifestContentAppDefinition definition, IIOHelper ioHelper) + public LegacyManifestContentAppFactory(LegacyManifestContentAppDefinition definition, IIOHelper ioHelper) { _definition = definition; _ioHelper = ioHelper; diff --git a/src/Umbraco.Core/Manifest/ManifestDashboard.cs b/src/Umbraco.Core/Manifest/LegacyManifestDashboard.cs similarity index 92% rename from src/Umbraco.Core/Manifest/ManifestDashboard.cs rename to src/Umbraco.Core/Manifest/LegacyManifestDashboard.cs index 75cdf24ebe..9cc0ad9091 100644 --- a/src/Umbraco.Core/Manifest/ManifestDashboard.cs +++ b/src/Umbraco.Core/Manifest/LegacyManifestDashboard.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Dashboards; namespace Umbraco.Cms.Core.Manifest; [DataContract] -public class ManifestDashboard : IDashboard +public class LegacyManifestDashboard : IDashboard { [DataMember(Name = "weight")] public int Weight { get; set; } = 100; diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs b/src/Umbraco.Core/Manifest/LegacyManifestFilterCollection.cs similarity index 56% rename from src/Umbraco.Core/Manifest/ManifestFilterCollection.cs rename to src/Umbraco.Core/Manifest/LegacyManifestFilterCollection.cs index a1d5cac0c1..a2af5ee440 100644 --- a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs +++ b/src/Umbraco.Core/Manifest/LegacyManifestFilterCollection.cs @@ -5,9 +5,9 @@ namespace Umbraco.Cms.Core.Manifest; /// /// Contains the manifest filters. /// -public class ManifestFilterCollection : BuilderCollectionBase +public class LegacyManifestFilterCollection : BuilderCollectionBase { - public ManifestFilterCollection(Func> items) + public LegacyManifestFilterCollection(Func> items) : base(items) { } @@ -16,9 +16,9 @@ public class ManifestFilterCollection : BuilderCollectionBase /// Filters package manifests. /// /// The package manifests. - public void Filter(List manifests) + public void Filter(List manifests) { - foreach (IManifestFilter filter in this) + foreach (ILegacyManifestFilter filter in this) { filter.Filter(manifests); } diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs b/src/Umbraco.Core/Manifest/LegacyManifestFilterCollectionBuilder.cs similarity index 50% rename from src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs rename to src/Umbraco.Core/Manifest/LegacyManifestFilterCollectionBuilder.cs index 5f012d10c9..ae8c41a9f0 100644 --- a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs +++ b/src/Umbraco.Core/Manifest/LegacyManifestFilterCollectionBuilder.cs @@ -3,10 +3,10 @@ using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Manifest; -public class ManifestFilterCollectionBuilder : OrderedCollectionBuilderBase +public class LegacyManifestFilterCollectionBuilder : OrderedCollectionBuilderBase { - protected override ManifestFilterCollectionBuilder This => this; + protected override LegacyManifestFilterCollectionBuilder This => this; // do NOT cache this, it's only used once protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; diff --git a/src/Umbraco.Core/Manifest/ManifestSection.cs b/src/Umbraco.Core/Manifest/LegacyManifestSection.cs similarity index 87% rename from src/Umbraco.Core/Manifest/ManifestSection.cs rename to src/Umbraco.Core/Manifest/LegacyManifestSection.cs index c7671c91e2..e0b0446523 100644 --- a/src/Umbraco.Core/Manifest/ManifestSection.cs +++ b/src/Umbraco.Core/Manifest/LegacyManifestSection.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Sections; namespace Umbraco.Cms.Core.Manifest; [DataContract(Name = "section", Namespace = "")] -public class ManifestSection : ISection +public class LegacyManifestSection : ISection { [DataMember(Name = "alias")] public string Alias { get; set; } = string.Empty; diff --git a/src/Umbraco.Core/Manifest/LegacyPackageManifest.cs b/src/Umbraco.Core/Manifest/LegacyPackageManifest.cs new file mode 100644 index 0000000000..64fd3f6267 --- /dev/null +++ b/src/Umbraco.Core/Manifest/LegacyPackageManifest.cs @@ -0,0 +1,115 @@ +using System.Runtime.Serialization; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Manifest; + +/// +/// Represents the content of a package manifest. +/// +[DataContract] +public class LegacyPackageManifest +{ + private string? _packageName; + + /// + /// An optional package name. If not specified then the directory name is used. + /// + [DataMember(Name = "name")] + public string? PackageName + { + get + { + if (!_packageName.IsNullOrWhiteSpace()) + { + return _packageName; + } + + if (!Source.IsNullOrWhiteSpace()) + { + _packageName = Path.GetFileName(Path.GetDirectoryName(Source)); + } + + return _packageName; + } + set => _packageName = value; + } + + [DataMember(Name = "packageView")] + public string? PackageView { get; set; } + + /// + /// Gets the source path of the manifest. + /// + /// + /// + /// Gets the full absolute file path of the manifest, + /// using system directory separators. + /// + /// + [IgnoreDataMember] + public string Source { get; set; } = null!; + + /// + /// Gets or sets the version of the package + /// + [DataMember(Name = "version")] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether telemetry is allowed + /// + [DataMember(Name = "allowPackageTelemetry")] + public bool AllowPackageTelemetry { get; set; } = true; + + [DataMember(Name = "bundleOptions")] + public BundleOptions BundleOptions { get; set; } + + /// + /// Gets or sets the scripts listed in the manifest. + /// + [DataMember(Name = "javascript")] + public string[] Scripts { get; set; } = Array.Empty(); + + /// + /// Gets or sets the stylesheets listed in the manifest. + /// + [DataMember(Name = "css")] + public string[] Stylesheets { get; set; } = Array.Empty(); + + /// + /// Gets or sets the property editors listed in the manifest. + /// + [DataMember(Name = "propertyEditors")] + public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); + + /// + /// Gets or sets the parameter editors listed in the manifest. + /// + [DataMember(Name = "parameterEditors")] + public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); + + /// + /// Gets or sets the grid editors listed in the manifest. + /// + [DataMember(Name = "gridEditors")] + public GridEditor[] GridEditors { get; set; } = Array.Empty(); + + /// + /// Gets or sets the content apps listed in the manifest. + /// + [DataMember(Name = "contentApps")] + public LegacyManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); + + /// + /// Gets or sets the dashboards listed in the manifest. + /// + [DataMember(Name = "dashboards")] + public LegacyManifestDashboard[] Dashboards { get; set; } = Array.Empty(); + + /// + /// Gets or sets the sections listed in the manifest. + /// + [DataMember(Name = "sections")] + public LegacyManifestSection[] Sections { get; set; } = Array.Empty(); +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 7bf07cfde9..ba62334240 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,115 +1,12 @@ -using System.Runtime.Serialization; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; +namespace Umbraco.Cms.Core.Manifest; -namespace Umbraco.Cms.Core.Manifest; - -/// -/// Represents the content of a package manifest. -/// -[DataContract] public class PackageManifest { - private string? _packageName; + public required string Name { get; set; } - /// - /// An optional package name. If not specified then the directory name is used. - /// - [DataMember(Name = "name")] - public string? PackageName - { - get - { - if (!_packageName.IsNullOrWhiteSpace()) - { - return _packageName; - } + public string? Version { get; set; } - if (!Source.IsNullOrWhiteSpace()) - { - _packageName = Path.GetFileName(Path.GetDirectoryName(Source)); - } + public bool AllowTelemetry { get; set; } = true; - return _packageName; - } - set => _packageName = value; - } - - [DataMember(Name = "packageView")] - public string? PackageView { get; set; } - - /// - /// Gets the source path of the manifest. - /// - /// - /// - /// Gets the full absolute file path of the manifest, - /// using system directory separators. - /// - /// - [IgnoreDataMember] - public string Source { get; set; } = null!; - - /// - /// Gets or sets the version of the package - /// - [DataMember(Name = "version")] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether telemetry is allowed - /// - [DataMember(Name = "allowPackageTelemetry")] - public bool AllowPackageTelemetry { get; set; } = true; - - [DataMember(Name = "bundleOptions")] - public BundleOptions BundleOptions { get; set; } - - /// - /// Gets or sets the scripts listed in the manifest. - /// - [DataMember(Name = "javascript")] - public string[] Scripts { get; set; } = Array.Empty(); - - /// - /// Gets or sets the stylesheets listed in the manifest. - /// - [DataMember(Name = "css")] - public string[] Stylesheets { get; set; } = Array.Empty(); - - /// - /// Gets or sets the property editors listed in the manifest. - /// - [DataMember(Name = "propertyEditors")] - public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the parameter editors listed in the manifest. - /// - [DataMember(Name = "parameterEditors")] - public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the grid editors listed in the manifest. - /// - [DataMember(Name = "gridEditors")] - public GridEditor[] GridEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the content apps listed in the manifest. - /// - [DataMember(Name = "contentApps")] - public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); - - /// - /// Gets or sets the dashboards listed in the manifest. - /// - [DataMember(Name = "dashboards")] - public ManifestDashboard[] Dashboards { get; set; } = Array.Empty(); - - /// - /// Gets or sets the sections listed in the manifest. - /// - [DataMember(Name = "sections")] - public ManifestSection[] Sections { get; set; } = Array.Empty(); + public required object[] Extensions { get; set; } } diff --git a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs index c64af5ac0a..7e943d1840 100644 --- a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs @@ -20,7 +20,7 @@ public class SectionMapDefinition : IMapDefinition // this is for AutoMapper ReverseMap - but really? mapper.Define(); mapper.Define(); - mapper.Define(Map); + mapper.Define(Map); mapper.Define(); mapper.Define(); mapper.Define(); @@ -30,7 +30,7 @@ public class SectionMapDefinition : IMapDefinition } // Umbraco.Code.MapAll - private static void Map(Section source, ManifestSection target, MapperContext context) + private static void Map(Section source, LegacyManifestSection target, MapperContext context) { target.Alias = source.Alias; target.Name = source.Name; diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs index eec435ddf6..746a4e2c5e 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs @@ -5,10 +5,10 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class ParameterEditorCollection : BuilderCollectionBase { - public ParameterEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) + public ParameterEditorCollection(DataEditorCollection dataEditors, ILegacyManifestParser legacyManifestParser) : base(() => dataEditors .Where(x => (x.Type & EditorType.MacroParameter) > 0) - .Union(manifestParser.CombinedManifest.PropertyEditors)) + .Union(legacyManifestParser.CombinedManifest.PropertyEditors)) { } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs index ff700431d5..04a111733c 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs @@ -6,10 +6,10 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class PropertyEditorCollection : BuilderCollectionBase { - public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) + public PropertyEditorCollection(DataEditorCollection dataEditors, ILegacyManifestParser legacyManifestParser) : base(() => dataEditors .Where(x => (x.Type & EditorType.PropertyValue) > 0) - .Union(manifestParser.CombinedManifest.PropertyEditors)) + .Union(legacyManifestParser.CombinedManifest.PropertyEditors)) { } diff --git a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs index 7644b1cc8c..0b8971d223 100644 --- a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs +++ b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs @@ -14,8 +14,8 @@ public class // get the manifest parser just-in-time - injecting it in the ctor would mean that // simply getting the builder in order to configure the collection, would require // its dependencies too, and that can create cycles or other oddities - IManifestParser manifestParser = factory.GetRequiredService(); + ILegacyManifestParser legacyManifestParser = factory.GetRequiredService(); - return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.Sections); + return base.CreateItems(factory).Concat(legacyManifestParser.CombinedManifest.Sections); } } diff --git a/src/Umbraco.Core/Telemetry/ITelemetryService.cs b/src/Umbraco.Core/Telemetry/ITelemetryService.cs index 23b0d154a4..b018cd4c40 100644 --- a/src/Umbraco.Core/Telemetry/ITelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/ITelemetryService.cs @@ -7,8 +7,14 @@ namespace Umbraco.Cms.Core.Telemetry; /// public interface ITelemetryService { - /// - /// Try and get the - /// + [Obsolete("Please use GetTelemetryReportDataAsync. Will be removed in V15.")] bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData); + + /// + /// Attempts to get the + /// + /// + /// May return null if the site is in an unknown state. + /// + Task GetTelemetryReportDataAsync(); } diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs index 4ebf1ba0b9..1d5fde2996 100644 --- a/src/Umbraco.Core/Telemetry/TelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs @@ -1,7 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; @@ -13,82 +15,106 @@ namespace Umbraco.Cms.Core.Telemetry; /// internal class TelemetryService : ITelemetryService { - private readonly IManifestParser _manifestParser; private readonly IMetricsConsentService _metricsConsentService; private readonly ISiteIdentifierService _siteIdentifierService; private readonly IUmbracoVersion _umbracoVersion; private readonly IUsageInformationService _usageInformationService; + private readonly IPackageManifestService _packageManifestService; + + [Obsolete("Please use the constructor that does not take an IManifestParser. Will be removed in V15.")] + public TelemetryService( + ILegacyManifestParser legacyManifestParser, + IUmbracoVersion umbracoVersion, + ISiteIdentifierService siteIdentifierService, + IUsageInformationService usageInformationService, + IMetricsConsentService metricsConsentService) + : this( + legacyManifestParser, + umbracoVersion, + siteIdentifierService, + usageInformationService, + metricsConsentService, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [Obsolete("Please use the constructor that does not take an IManifestParser. Will be removed in V15.")] + public TelemetryService( + ILegacyManifestParser legacyManifestParser, + IUmbracoVersion umbracoVersion, + ISiteIdentifierService siteIdentifierService, + IUsageInformationService usageInformationService, + IMetricsConsentService metricsConsentService, + IPackageManifestService packageManifestService) + : this( + umbracoVersion, + siteIdentifierService, + usageInformationService, + metricsConsentService, + packageManifestService) + { + } /// /// Initializes a new instance of the class. /// public TelemetryService( - IManifestParser manifestParser, IUmbracoVersion umbracoVersion, ISiteIdentifierService siteIdentifierService, IUsageInformationService usageInformationService, - IMetricsConsentService metricsConsentService) + IMetricsConsentService metricsConsentService, + IPackageManifestService packageManifestService) { - _manifestParser = manifestParser; _umbracoVersion = umbracoVersion; _siteIdentifierService = siteIdentifierService; _usageInformationService = usageInformationService; _metricsConsentService = metricsConsentService; + _packageManifestService = packageManifestService; + } + + [Obsolete("Please use GetTelemetryReportDataAsync. Will be removed in V15.")] + public bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData) + { + telemetryReportData = GetTelemetryReportDataAsync().GetAwaiter().GetResult(); + return telemetryReportData != null; } /// - public bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData) + public async Task GetTelemetryReportDataAsync() { if (_siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid telemetryId) is false) { - telemetryReportData = null; - return false; + return null; } - telemetryReportData = new TelemetryReportData + return new TelemetryReportData { Id = telemetryId, Version = GetVersion(), - Packages = GetPackageTelemetry(), + Packages = await GetPackageTelemetryAsync(), Detailed = _usageInformationService.GetDetailed(), }; - return true; } - private string? GetVersion() + private string? GetVersion() => _metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal + ? null + : _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); + + private async Task?> GetPackageTelemetryAsync() { if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) { return null; } - return _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); - } + IEnumerable manifests = await _packageManifestService.GetPackageManifestsAsync(); - private IEnumerable? GetPackageTelemetry() - { - if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) - { - return null; - } - - List packages = new(); - IEnumerable manifests = _manifestParser.GetManifests(); - - foreach (PackageManifest manifest in manifests) - { - if (manifest.AllowPackageTelemetry is false) + return manifests + .Where(manifest => manifest.AllowTelemetry) + .Select(manifest => new PackageTelemetry { - continue; - } - - packages.Add(new PackageTelemetry - { - Name = manifest.PackageName, - Version = manifest.Version ?? string.Empty, + Name = manifest.Name, + Version = manifest.Version ?? string.Empty }); - } - - return packages; } } diff --git a/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml b/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml index fc303216ba..c015f419d5 100644 --- a/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml +++ b/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml @@ -21,6 +21,13 @@ lib/net7.0/Umbraco.Infrastructure.dll true + + CP0001 + T:Umbraco.Cms.Core.Manifest.ManifestParser + lib/net7.0/Umbraco.Infrastructure.dll + lib/net7.0/Umbraco.Infrastructure.dll + true + CP0001 T:Umbraco.Cms.Infrastructure.Migrations.PostMigrations.ClearCsrfCookies @@ -84,6 +91,13 @@ lib/net7.0/Umbraco.Infrastructure.dll true + + CP0002 + M:Umbraco.Cms.Core.Services.Implement.PackagingService.#ctor(Umbraco.Cms.Core.Services.IAuditService,Umbraco.Cms.Core.Packaging.ICreatedPackagesRepository,Umbraco.Cms.Core.Packaging.IPackageInstallation,Umbraco.Cms.Core.Events.IEventAggregator,Umbraco.Cms.Core.Manifest.IManifestParser,Umbraco.Cms.Core.Services.IKeyValueService,Umbraco.Cms.Core.Packaging.PackageMigrationPlanCollection) + lib/net7.0/Umbraco.Infrastructure.dll + lib/net7.0/Umbraco.Infrastructure.dll + true + CP0002 M:Umbraco.Cms.Infrastructure.Install.PackageMigrationRunner.#ctor(Umbraco.Cms.Core.Logging.IProfilingLogger,Umbraco.Cms.Core.Scoping.ICoreScopeProvider,Umbraco.Cms.Core.Packaging.PendingPackageMigrations,Umbraco.Cms.Core.Packaging.PackageMigrationPlanCollection,Umbraco.Cms.Core.Migrations.IMigrationPlanExecutor,Umbraco.Cms.Core.Services.IKeyValueService,Umbraco.Cms.Core.Events.IEventAggregator) @@ -140,6 +154,27 @@ lib/net7.0/Umbraco.Infrastructure.dll true + + CP0002 + M:Umbraco.Cms.Infrastructure.WebAssets.BackOfficeWebAssets.#ctor(Umbraco.Cms.Core.WebAssets.IRuntimeMinifier,Umbraco.Cms.Core.Manifest.IManifestParser,Umbraco.Cms.Core.PropertyEditors.PropertyEditorCollection,Umbraco.Cms.Core.Hosting.IHostingEnvironment,Microsoft.Extensions.Options.IOptionsMonitor{Umbraco.Cms.Core.Configuration.Models.GlobalSettings},Umbraco.Cms.Core.WebAssets.CustomBackOfficeAssetsCollection) + lib/net7.0/Umbraco.Infrastructure.dll + lib/net7.0/Umbraco.Infrastructure.dll + true + + + CP0002 + M:Umbraco.Cms.Infrastructure.WebAssets.BackOfficeWebAssets.GetIndependentPackageBundleName(Umbraco.Cms.Core.Manifest.ManifestAssets,Umbraco.Cms.Core.WebAssets.AssetType) + lib/net7.0/Umbraco.Infrastructure.dll + lib/net7.0/Umbraco.Infrastructure.dll + true + + + CP0005 + M:Umbraco.Cms.Core.Security.UmbracoUserStore`2.ResolveEntityIdFromIdentityId(System.String) + lib/net7.0/Umbraco.Infrastructure.dll + lib/net7.0/Umbraco.Infrastructure.dll + true + CP0006 M:Umbraco.Cms.Core.Deploy.IGridCellValueConnector.GetValue(Umbraco.Cms.Core.Models.GridValue.GridControl,System.Collections.Generic.ICollection{Umbraco.Cms.Core.Deploy.ArtifactDependency},Umbraco.Cms.Core.Deploy.IContextCache) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 0b24a474b4..73f0b0da49 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -43,6 +43,7 @@ using Umbraco.Cms.Infrastructure.HealthChecks; using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.Install; using Umbraco.Cms.Infrastructure.Mail; +using Umbraco.Cms.Infrastructure.Manifest; using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Migrations.PostMigrations; @@ -126,7 +127,9 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddTransient(); // register manifest parser, will be injected in collection builders where needed - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // register the manifest filter collection builder (collection is empty by default) builder.ManifestFilters(); diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index 95997dbbf5..47f1afe650 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -40,18 +40,20 @@ public class ReportSiteTask : RecurringHostedServiceBase { } - /// - /// Runs the background task to send the anonymous ID - /// to telemetry service - /// - public override async Task PerformExecuteAsync(object? state){ + /// + /// Runs the background task to send the anonymous ID + /// to telemetry service + /// + public override async Task PerformExecuteAsync(object? state) + { if (_runtimeState.Level is not RuntimeLevel.Run) { // We probably haven't installed yet, so we can't get telemetry. return; } - if (_telemetryService.TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData) is false) + TelemetryReportData? telemetryReportData = await _telemetryService.GetTelemetryReportDataAsync(); + if (telemetryReportData is null) { _logger.LogWarning("No telemetry marker found"); diff --git a/src/Umbraco.Infrastructure/Manifest/IPackageManifestReader.cs b/src/Umbraco.Infrastructure/Manifest/IPackageManifestReader.cs new file mode 100644 index 0000000000..806bb3bfb5 --- /dev/null +++ b/src/Umbraco.Infrastructure/Manifest/IPackageManifestReader.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Core.Manifest; + +namespace Umbraco.Cms.Infrastructure.Manifest; + +public interface IPackageManifestReader +{ + Task> ReadPackageManifestsAsync(); +} diff --git a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs b/src/Umbraco.Infrastructure/Manifest/LegacyManifestParser.cs similarity index 78% rename from src/Umbraco.Infrastructure/Manifest/ManifestParser.cs rename to src/Umbraco.Infrastructure/Manifest/LegacyManifestParser.cs index 887ac05dc4..916378f201 100644 --- a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs +++ b/src/Umbraco.Infrastructure/Manifest/LegacyManifestParser.cs @@ -19,40 +19,40 @@ namespace Umbraco.Cms.Core.Manifest; /// /// Parses the Main.js file and replaces all tokens accordingly. /// -public class ManifestParser : IManifestParser +public class LegacyManifestParser : ILegacyManifestParser { private static readonly string _utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); private readonly IAppPolicyCache _cache; private readonly IDataValueEditorFactory _dataValueEditorFactory; - private readonly IManifestFileProviderFactory _manifestFileProviderFactory; - private readonly ManifestFilterCollection _filters; + private readonly ILegacyPackageManifestFileProviderFactory _legacyPackageManifestFileProviderFactory; + private readonly LegacyManifestFilterCollection _filters; private readonly IHostingEnvironment _hostingEnvironment; private readonly IIOHelper _ioHelper; private readonly IJsonSerializer _jsonSerializer; private readonly ILocalizedTextService _localizedTextService; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IShortStringHelper _shortStringHelper; private readonly ManifestValueValidatorCollection _validators; private string _path = null!; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ManifestParser( + public LegacyManifestParser( AppCaches appCaches, ManifestValueValidatorCollection validators, - ManifestFilterCollection filters, - ILogger logger, + LegacyManifestFilterCollection filters, + ILogger logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IJsonSerializer jsonSerializer, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IDataValueEditorFactory dataValueEditorFactory, - IManifestFileProviderFactory manifestFileProviderFactory) + ILegacyPackageManifestFileProviderFactory legacyPackageManifestFileProviderFactory) { if (appCaches == null) { @@ -70,15 +70,15 @@ public class ManifestParser : IManifestParser _localizedTextService = localizedTextService; _shortStringHelper = shortStringHelper; _dataValueEditorFactory = dataValueEditorFactory; - _manifestFileProviderFactory = manifestFileProviderFactory; + _legacyPackageManifestFileProviderFactory = legacyPackageManifestFileProviderFactory; } [Obsolete("Use other ctor - Will be removed in Umbraco 13")] - public ManifestParser( + public LegacyManifestParser( AppCaches appCaches, ManifestValueValidatorCollection validators, - ManifestFilterCollection filters, - ILogger logger, + LegacyManifestFilterCollection filters, + ILogger logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IJsonSerializer jsonSerializer, @@ -96,7 +96,7 @@ public class ManifestParser : IManifestParser localizedTextService, shortStringHelper, dataValueEditorFactory, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService()) { } @@ -110,20 +110,20 @@ public class ManifestParser : IManifestParser /// Gets all manifests, merged into a single manifest object. /// /// - public CompositePackageManifest CombinedManifest + public CompositeLegacyPackageManifest CombinedManifest => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => { - IEnumerable manifests = GetManifests(); + IEnumerable manifests = GetManifests(); return MergeManifests(manifests); }, new TimeSpan(0, 4, 0))!; /// /// Gets all manifests. /// - public IEnumerable GetManifests() + public IEnumerable GetManifests() { - var manifests = new List(); - IFileProvider? manifestFileProvider = _manifestFileProviderFactory.Create(); + var manifests = new List(); + IFileProvider? manifestFileProvider = _legacyPackageManifestFileProviderFactory.Create(); if (manifestFileProvider is null) { @@ -143,7 +143,7 @@ public class ManifestParser : IManifestParser continue; } - PackageManifest manifest = ParseManifest(text); + LegacyPackageManifest manifest = ParseManifest(text); manifest.Source = file.PhysicalPath!; // We assure that the PhysicalPath is not null in GetManifestFiles() manifests.Add(manifest); } @@ -161,7 +161,7 @@ public class ManifestParser : IManifestParser /// /// Parses a manifest. /// - public PackageManifest ParseManifest(string text) + public LegacyPackageManifest ParseManifest(string text) { if (text == null) { @@ -173,7 +173,7 @@ public class ManifestParser : IManifestParser throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text)); } - PackageManifest? manifest = JsonConvert.DeserializeObject( + LegacyPackageManifest? manifest = JsonConvert.DeserializeObject( text, new DataEditorConverter(_dataValueEditorFactory, _ioHelper, _localizedTextService, _shortStringHelper, _jsonSerializer), new ValueValidatorConverter(_validators), @@ -190,12 +190,12 @@ public class ManifestParser : IManifestParser manifest.Stylesheets[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Stylesheets[i])!; } - foreach (ManifestContentAppDefinition contentApp in manifest.ContentApps) + foreach (LegacyManifestContentAppDefinition contentApp in manifest.ContentApps) { contentApp.View = _ioHelper.ResolveRelativeOrVirtualUrl(contentApp.View); } - foreach (ManifestDashboard dashboard in manifest.Dashboards) + foreach (LegacyManifestDashboard dashboard in manifest.Dashboards) { dashboard.View = _ioHelper.ResolveRelativeOrVirtualUrl(dashboard.View)!; } @@ -220,34 +220,34 @@ public class ManifestParser : IManifestParser /// /// Merges all manifests into one. /// - private static CompositePackageManifest MergeManifests(IEnumerable manifests) + private static CompositeLegacyPackageManifest MergeManifests(IEnumerable manifests) { - var scripts = new Dictionary>(); - var stylesheets = new Dictionary>(); + var scripts = new Dictionary>(); + var stylesheets = new Dictionary>(); var propertyEditors = new List(); var parameterEditors = new List(); var gridEditors = new List(); - var contentApps = new List(); - var dashboards = new List(); - var sections = new List(); + var contentApps = new List(); + var dashboards = new List(); + var sections = new List(); - foreach (PackageManifest manifest in manifests) + foreach (LegacyPackageManifest manifest in manifests) { - if (!scripts.TryGetValue(manifest.BundleOptions, out List? scriptsPerBundleOption)) + if (!scripts.TryGetValue(manifest.BundleOptions, out List? scriptsPerBundleOption)) { - scriptsPerBundleOption = new List(); + scriptsPerBundleOption = new List(); scripts[manifest.BundleOptions] = scriptsPerBundleOption; } - scriptsPerBundleOption.Add(new ManifestAssets(manifest.PackageName, manifest.Scripts)); + scriptsPerBundleOption.Add(new LegacyManifestAssets(manifest.PackageName, manifest.Scripts)); - if (!stylesheets.TryGetValue(manifest.BundleOptions, out List? stylesPerBundleOption)) + if (!stylesheets.TryGetValue(manifest.BundleOptions, out List? stylesPerBundleOption)) { - stylesPerBundleOption = new List(); + stylesPerBundleOption = new List(); stylesheets[manifest.BundleOptions] = stylesPerBundleOption; } - stylesPerBundleOption.Add(new ManifestAssets(manifest.PackageName, manifest.Stylesheets)); + stylesPerBundleOption.Add(new LegacyManifestAssets(manifest.PackageName, manifest.Stylesheets)); propertyEditors.AddRange(manifest.PropertyEditors); @@ -262,15 +262,15 @@ public class ManifestParser : IManifestParser sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias, StringComparer.OrdinalIgnoreCase)); } - return new CompositePackageManifest( + return new CompositeLegacyPackageManifest( propertyEditors, parameterEditors, gridEditors, contentApps, dashboards, sections, - scripts.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value), - stylesheets.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value)); + scripts.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value), + stylesheets.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value)); } private static string TrimPreamble(string text) diff --git a/src/Umbraco.Infrastructure/Manifest/PackageManifestReader.cs b/src/Umbraco.Infrastructure/Manifest/PackageManifestReader.cs new file mode 100644 index 0000000000..c87f1ebecf --- /dev/null +++ b/src/Umbraco.Infrastructure/Manifest/PackageManifestReader.cs @@ -0,0 +1,99 @@ +using System.Text; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Manifest; + +internal sealed class AppPluginsFileProviderPackageManifestReader : IPackageManifestReader +{ + private readonly IPackageManifestFileProviderFactory _packageManifestFileProviderFactory; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + + public AppPluginsFileProviderPackageManifestReader( + IPackageManifestFileProviderFactory packageManifestFileProviderFactory, + IJsonSerializer jsonSerializer, + ILogger logger) + { + _packageManifestFileProviderFactory = packageManifestFileProviderFactory; + _jsonSerializer = jsonSerializer; + _logger = logger; + } + + public async Task> ReadPackageManifestsAsync() + { + IFileProvider? fileProvider = _packageManifestFileProviderFactory.Create(); + if (fileProvider is null) + { + throw new ArgumentNullException(nameof(fileProvider)); + } + + IFileInfo[] files = GetAllPackageManifestFiles(fileProvider, Constants.SystemDirectories.AppPlugins).ToArray(); + return await ParsePackageManifestFiles(files); + } + + private static IEnumerable GetAllPackageManifestFiles(IFileProvider fileProvider, string path) + { + const string extensionFileName = "umbraco-package.json"; + foreach (IFileInfo fileInfo in fileProvider.GetDirectoryContents(path)) + { + if (fileInfo.IsDirectory) + { + var virtualPath = WebPath.Combine(path, fileInfo.Name); + + // find all extension package configuration files recursively + foreach (IFileInfo nested in GetAllPackageManifestFiles(fileProvider, virtualPath)) + { + yield return nested; + } + } + else if (fileInfo.Name.InvariantEquals(extensionFileName)) + { + yield return fileInfo; + } + } + } + + private async Task> ParsePackageManifestFiles(IFileInfo[] files) + { + var packageManifests = new List(); + foreach (IFileInfo fileInfo in files) + { + string fileContent; + await using (Stream stream = fileInfo.CreateReadStream()) + { + using (var reader = new StreamReader(stream, Encoding.UTF8)) + { + fileContent = await reader.ReadToEndAsync(); + } + } + + if (fileContent.IsNullOrWhiteSpace()) + { + continue; + } + + try + { + PackageManifest? packageManifest = _jsonSerializer.Deserialize(fileContent); + if (packageManifest != null) + { + packageManifests.Add(packageManifest); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Unable to load package manifest file: {FileName}", fileInfo.Name); + throw; + } + } + + return packageManifests; + } +} diff --git a/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs new file mode 100644 index 0000000000..70c691abe9 --- /dev/null +++ b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Manifest; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Manifest; + +internal sealed class PackageManifestService : IPackageManifestService +{ + private readonly IEnumerable _packageManifestReaders; + private readonly IAppPolicyCache _cache; + private readonly PackageManifestSettings _packageManifestSettings; + + public PackageManifestService( + IEnumerable packageManifestReaders, + AppCaches appCaches, + IOptions packageManifestSettings) + { + _packageManifestReaders = packageManifestReaders; + _packageManifestSettings = packageManifestSettings.Value; + _cache = appCaches.RuntimeCache; + } + + public async Task> GetPackageManifestsAsync() + => await _cache.GetCacheItemAsync( + $"{nameof(PackageManifestService)}-PackageManifests", + async () => + { + Task>[] tasks = _packageManifestReaders + .Select(x => x.ReadPackageManifestsAsync()) + .ToArray(); + await Task.WhenAll(tasks); + + return tasks.SelectMany(x => x.Result); + }, + _packageManifestSettings.CacheTimeout) + ?? Array.Empty(); +} diff --git a/src/Umbraco.Infrastructure/Serialization/ContextualJsonSerializer.cs b/src/Umbraco.Infrastructure/Serialization/ContextualJsonSerializer.cs index 4583c502ab..2880611ccb 100644 --- a/src/Umbraco.Infrastructure/Serialization/ContextualJsonSerializer.cs +++ b/src/Umbraco.Infrastructure/Serialization/ContextualJsonSerializer.cs @@ -47,3 +47,4 @@ public class ContextualJsonSerializer : IJsonSerializer } } + diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs index be3fe9d629..cc40b5ae73 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs @@ -26,7 +26,7 @@ public class PackagingService : IPackagingService private readonly ICreatedPackagesRepository _createdPackages; private readonly IEventAggregator _eventAggregator; private readonly IKeyValueService _keyValueService; - private readonly IManifestParser _manifestParser; + private readonly ILegacyManifestParser _legacyManifestParser; private readonly IPackageInstallation _packageInstallation; private readonly PackageMigrationPlanCollection _packageMigrationPlans; private readonly IHostEnvironment _hostEnvironment; @@ -36,7 +36,7 @@ public class PackagingService : IPackagingService ICreatedPackagesRepository createdPackages, IPackageInstallation packageInstallation, IEventAggregator eventAggregator, - IManifestParser manifestParser, + ILegacyManifestParser legacyManifestParser, IKeyValueService keyValueService, PackageMigrationPlanCollection packageMigrationPlans, IHostEnvironment hostEnvironment) @@ -45,7 +45,7 @@ public class PackagingService : IPackagingService _createdPackages = createdPackages; _packageInstallation = packageInstallation; _eventAggregator = eventAggregator; - _manifestParser = manifestParser; + _legacyManifestParser = legacyManifestParser; _keyValueService = keyValueService; _packageMigrationPlans = packageMigrationPlans; _hostEnvironment = hostEnvironment; @@ -57,7 +57,7 @@ public class PackagingService : IPackagingService ICreatedPackagesRepository createdPackages, IPackageInstallation packageInstallation, IEventAggregator eventAggregator, - IManifestParser manifestParser, + ILegacyManifestParser manifestParser, IKeyValueService keyValueService, PackageMigrationPlanCollection packageMigrationPlans) : this( @@ -199,7 +199,7 @@ public class PackagingService : IPackagingService .Items.ToDictionary(package => package.PackageName!, package => package); // PackageName cannot be null here // Collect and merge the packages from the manifests - foreach (PackageManifest package in _manifestParser.GetManifests()) + foreach (LegacyPackageManifest package in _legacyManifestParser.GetManifests()) { if (package.PackageName is null) { diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs index ab25dc691c..3bb9872901 100644 --- a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs +++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs @@ -24,7 +24,7 @@ public class BackOfficeWebAssets public const string UmbracoUpgradeCssBundleName = "umbraco-authorize-upgrade-css"; private readonly CustomBackOfficeAssetsCollection _customBackOfficeAssetsCollection; private readonly IHostingEnvironment _hostingEnvironment; - private readonly IManifestParser _parser; + private readonly ILegacyManifestParser _parser; private readonly PropertyEditorCollection _propertyEditorCollection; private readonly IRuntimeMinifier _runtimeMinifier; @@ -32,7 +32,7 @@ public class BackOfficeWebAssets public BackOfficeWebAssets( IRuntimeMinifier runtimeMinifier, - IManifestParser parser, + ILegacyManifestParser parser, PropertyEditorCollection propertyEditorCollection, IHostingEnvironment hostingEnvironment, IOptionsMonitor globalSettings, @@ -48,8 +48,8 @@ public class BackOfficeWebAssets globalSettings.OnChange(x => _globalSettings = x); } - public static string GetIndependentPackageBundleName(ManifestAssets manifestAssets, AssetType assetType) - => $"{manifestAssets.PackageName.ToLowerInvariant()}-{(assetType == AssetType.Css ? "css" : "js")}"; + public static string GetIndependentPackageBundleName(LegacyManifestAssets legacyManifestAssets, AssetType assetType) + => $"{legacyManifestAssets.PackageName.ToLowerInvariant()}-{(assetType == AssetType.Css ? "css" : "js")}"; public void CreateBundles() { @@ -151,7 +151,7 @@ public class BackOfficeWebAssets } private void RegisterPackageBundlesForNoneOption( - IReadOnlyDictionary>? combinedPackageManifestAssets, + IReadOnlyDictionary>? combinedPackageManifestAssets, string bundleName) { var assets = new HashSet(StringComparer.InvariantCultureIgnoreCase); @@ -159,7 +159,7 @@ public class BackOfficeWebAssets // Create a bundle per package manifest that is declaring the matching BundleOptions if (combinedPackageManifestAssets?.TryGetValue( BundleOptions.None, - out IReadOnlyList? manifestAssetList) ?? false) + out IReadOnlyList? manifestAssetList) ?? false) { foreach (var asset in manifestAssetList.SelectMany(x => x.Assets)) { @@ -176,15 +176,15 @@ public class BackOfficeWebAssets } private void RegisterPackageBundlesForIndependentOptions( - IReadOnlyDictionary>? combinedPackageManifestAssets, + IReadOnlyDictionary>? combinedPackageManifestAssets, AssetType assetType) { // Create a bundle per package manifest that is declaring the matching BundleOptions if (combinedPackageManifestAssets?.TryGetValue( BundleOptions.Independent, - out IReadOnlyList? manifestAssetList) ?? false) + out IReadOnlyList? manifestAssetList) ?? false) { - foreach (ManifestAssets manifestAssets in manifestAssetList) + foreach (LegacyManifestAssets manifestAssets in manifestAssetList) { var bundleName = GetIndependentPackageBundleName(manifestAssets, assetType); var filePaths = FormatPaths(manifestAssets.Assets.ToArray()); @@ -215,7 +215,7 @@ public class BackOfficeWebAssets // only include scripts with the default bundle options here if (_parser.CombinedManifest.Scripts.TryGetValue( BundleOptions.Default, - out IReadOnlyList? manifestAssets)) + out IReadOnlyList? manifestAssets)) { foreach (var script in manifestAssets.SelectMany(x => x.Assets)) { @@ -255,7 +255,7 @@ public class BackOfficeWebAssets // only include css with the default bundle options here if (_parser.CombinedManifest.Stylesheets.TryGetValue( BundleOptions.Default, - out IReadOnlyList? manifestAssets)) + out IReadOnlyList? manifestAssets)) { foreach (var script in manifestAssets.SelectMany(x => x.Assets)) { diff --git a/src/Umbraco.Web.BackOffice/CompatibilitySuppressions.xml b/src/Umbraco.Web.BackOffice/CompatibilitySuppressions.xml index a4d1773597..5003e09691 100644 --- a/src/Umbraco.Web.BackOffice/CompatibilitySuppressions.xml +++ b/src/Umbraco.Web.BackOffice/CompatibilitySuppressions.xml @@ -1,5 +1,12 @@  + + CP0002 + M:Umbraco.Cms.Web.BackOffice.Controllers.BackOfficeController.#ctor(Umbraco.Cms.Core.Security.IBackOfficeUserManager,Umbraco.Cms.Core.Services.IRuntimeState,Umbraco.Cms.Core.WebAssets.IRuntimeMinifier,Microsoft.Extensions.Options.IOptionsSnapshot{Umbraco.Cms.Core.Configuration.Models.GlobalSettings},Umbraco.Cms.Core.Hosting.IHostingEnvironment,Umbraco.Cms.Core.Services.ILocalizedTextService,Umbraco.Cms.Core.Configuration.Grid.IGridConfig,Umbraco.Cms.Web.BackOffice.Controllers.BackOfficeServerVariables,Umbraco.Cms.Core.Cache.AppCaches,Umbraco.Cms.Web.BackOffice.Security.IBackOfficeSignInManager,Umbraco.Cms.Core.Security.IBackOfficeSecurityAccessor,Microsoft.Extensions.Logging.ILogger{Umbraco.Cms.Web.BackOffice.Controllers.BackOfficeController},Umbraco.Cms.Core.Serialization.IJsonSerializer,Umbraco.Cms.Web.BackOffice.Security.IBackOfficeExternalLoginProviders,Microsoft.AspNetCore.Http.IHttpContextAccessor,Umbraco.Cms.Web.BackOffice.Security.IBackOfficeTwoFactorOptions,Umbraco.Cms.Core.Manifest.IManifestParser,Umbraco.Cms.Infrastructure.WebAssets.ServerVariablesParser,Microsoft.Extensions.Options.IOptions{Umbraco.Cms.Core.Configuration.Models.SecuritySettings}) + lib/net7.0/Umbraco.Web.BackOffice.dll + lib/net7.0/Umbraco.Web.BackOffice.dll + true + CP0002 M:Umbraco.Cms.Web.BackOffice.Controllers.ContentController.#ctor(Umbraco.Cms.Core.Dictionary.ICultureDictionary,Microsoft.Extensions.Logging.ILoggerFactory,Umbraco.Cms.Core.Strings.IShortStringHelper,Umbraco.Cms.Core.Events.IEventMessagesFactory,Umbraco.Cms.Core.Services.ILocalizedTextService,Umbraco.Cms.Core.PropertyEditors.PropertyEditorCollection,Umbraco.Cms.Core.Services.IContentService,Umbraco.Cms.Core.Services.IUserService,Umbraco.Cms.Core.Security.IBackOfficeSecurityAccessor,Umbraco.Cms.Core.Services.IContentTypeService,Umbraco.Cms.Core.Mapping.IUmbracoMapper,Umbraco.Cms.Core.Routing.IPublishedUrlProvider,Umbraco.Cms.Core.Services.IDomainService,Umbraco.Cms.Core.Services.IDataTypeService,Umbraco.Cms.Core.Services.ILocalizationService,Umbraco.Cms.Core.Services.IFileService,Umbraco.Cms.Core.Services.INotificationService,Umbraco.Cms.Core.Actions.ActionCollection,Umbraco.Cms.Infrastructure.Persistence.ISqlContext,Umbraco.Cms.Core.Serialization.IJsonSerializer,Umbraco.Cms.Core.Scoping.ICoreScopeProvider,Microsoft.AspNetCore.Authorization.IAuthorizationService,Umbraco.Cms.Core.Services.IContentVersionService) @@ -7,4 +14,11 @@ lib/net7.0/Umbraco.Web.BackOffice.dll true + + CP0002 + M:Umbraco.Extensions.RuntimeMinifierExtensions.GetScriptForLoadingBackOfficeAsync(Umbraco.Cms.Core.WebAssets.IRuntimeMinifier,Umbraco.Cms.Core.Configuration.Models.GlobalSettings,Umbraco.Cms.Core.Hosting.IHostingEnvironment,Umbraco.Cms.Core.Manifest.IManifestParser) + lib/net7.0/Umbraco.Web.BackOffice.dll + lib/net7.0/Umbraco.Web.BackOffice.dll + true + \ No newline at end of file diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index bd40d300c2..7a71242641 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -53,7 +53,7 @@ public class BackOfficeController : UmbracoController private readonly IHttpContextAccessor _httpContextAccessor; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; - private readonly IManifestParser _manifestParser; + private readonly ILegacyManifestParser _legacyManifestParser; private readonly IRuntimeMinifier _runtimeMinifier; private readonly IRuntimeState _runtimeState; private readonly IOptions _securitySettings; @@ -89,7 +89,7 @@ public class BackOfficeController : UmbracoController IBackOfficeExternalLoginProviders externalLogins, IHttpContextAccessor httpContextAccessor, IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, - IManifestParser manifestParser, + ILegacyManifestParser legacyManifestParser, ServerVariablesParser serverVariables, IOptions securitySettings) { @@ -109,7 +109,7 @@ public class BackOfficeController : UmbracoController _externalLogins = externalLogins; _httpContextAccessor = httpContextAccessor; _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; - _manifestParser = manifestParser; + _legacyManifestParser = legacyManifestParser; _serverVariables = serverVariables; _securitySettings = securitySettings; } @@ -236,7 +236,7 @@ public class BackOfficeController : UmbracoController var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync( _globalSettings, _hostingEnvironment, - _manifestParser); + _legacyManifestParser); return new JavaScriptResult(result); } diff --git a/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs index d00d0285e7..95ca6adc10 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs @@ -17,7 +17,7 @@ public static class RuntimeMinifierExtensions this IRuntimeMinifier minifier, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, - IManifestParser manifestParser) + ILegacyManifestParser legacyManifestParser) { var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName)) @@ -31,10 +31,10 @@ public static class RuntimeMinifierExtensions } // process the independent bundles - if (manifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Independent, - out IReadOnlyList? independentManifestAssetsList)) + if (legacyManifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Independent, + out IReadOnlyList? independentManifestAssetsList)) { - foreach (ManifestAssets manifestAssets in independentManifestAssetsList) + foreach (LegacyManifestAssets manifestAssets in independentManifestAssetsList) { var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Javascript); @@ -58,7 +58,7 @@ public static class RuntimeMinifierExtensions globalSettings, hostingEnvironment); - result += await GetStylesheetInitializationAsync(minifier, manifestParser); + result += await GetStylesheetInitializationAsync(minifier, legacyManifestParser); return result; } @@ -68,7 +68,7 @@ public static class RuntimeMinifierExtensions /// private static async Task GetStylesheetInitializationAsync( IRuntimeMinifier minifier, - IManifestParser manifestParser) + ILegacyManifestParser legacyManifestParser) { var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); foreach (var file in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName)) @@ -77,10 +77,10 @@ public static class RuntimeMinifierExtensions } // process the independent bundles - if (manifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Independent, - out IReadOnlyList? independentManifestAssetsList)) + if (legacyManifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Independent, + out IReadOnlyList? independentManifestAssetsList)) { - foreach (ManifestAssets manifestAssets in independentManifestAssetsList) + foreach (LegacyManifestAssets manifestAssets in independentManifestAssetsList) { var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Css); foreach (var asset in await minifier.GetCssAssetPathsAsync(bundleName)) diff --git a/src/Umbraco.Web.Common/CompatibilitySuppressions.xml b/src/Umbraco.Web.Common/CompatibilitySuppressions.xml new file mode 100644 index 0000000000..d1edc2b5e1 --- /dev/null +++ b/src/Umbraco.Web.Common/CompatibilitySuppressions.xml @@ -0,0 +1,10 @@ + + + + CP0002 + M:Umbraco.Cms.Web.Common.Profiler.WebProfiler.#ctor + lib/net7.0/Umbraco.Web.Common.dll + lib/net7.0/Umbraco.Web.Common.dll + true + + \ No newline at end of file diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index a1c62d0641..27c098e54f 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -151,8 +151,9 @@ public static partial class UmbracoBuilderExtensions // WebRootFileProviderFactory is just a wrapper around the IWebHostEnvironment.WebRootFileProvider, // therefore no need to register it as singleton - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now builder.Services.AddSingleton(factory => new DbProviderFactoryCreator( diff --git a/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs b/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs index 7c3831b919..22c86e49c2 100644 --- a/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs +++ b/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.IO; namespace Umbraco.Cms.Web.Common.FileProviders; -public class ContentAndWebRootFileProviderFactory : IManifestFileProviderFactory +public class ContentAndWebRootFileProviderFactory : ILegacyPackageManifestFileProviderFactory, IPackageManifestFileProviderFactory { private readonly IWebHostEnvironment _webHostEnvironment; diff --git a/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs b/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs index 64824dd090..02fe86f6fd 100644 --- a/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs +++ b/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.IO; namespace Umbraco.Cms.Web.Common.FileProviders; -public class WebRootFileProviderFactory : IManifestFileProviderFactory, IGridEditorsConfigFileProviderFactory +public class WebRootFileProviderFactory : ILegacyPackageManifestFileProviderFactory, IGridEditorsConfigFileProviderFactory { private readonly IWebHostEnvironment _webHostEnvironment; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs index 1b7efdd923..4da509f59b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs @@ -27,4 +27,25 @@ public abstract class RuntimeAppCacheTests : AppCacheTests Assert.AreEqual(default(DateTime), AppCache.GetCacheItem("DateTimeTest")); Assert.AreEqual(null, AppCache.GetCacheItem("DateTimeTest")); } + + [Test] + public async Task Can_Get_With_Async_Factory() + { + var value = await AppPolicyCache.GetCacheItemAsync("AsyncFactoryGetTest", async () => await GetValueAsync(5), TimeSpan.FromMilliseconds(100)); + Assert.AreEqual(50, value); + } + + [Test] + public async Task Can_Insert_With_Async_Factory() + { + await AppPolicyCache.InsertCacheItemAsync("AsyncFactoryInsertTest", async () => await GetValueAsync(10), TimeSpan.FromMilliseconds(100)); + var value = AppPolicyCache.GetCacheItem("AsyncFactoryInsertTest"); + Assert.AreEqual(100, value); + } + + private static async Task GetValueAsync(int value) + { + await Task.Delay(10); + return value * 10; + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestContentAppTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestContentAppTests.cs similarity index 94% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestContentAppTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestContentAppTests.cs index 350c1f1944..5d66ba7556 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestContentAppTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestContentAppTests.cs @@ -14,7 +14,7 @@ using Umbraco.Cms.Tests.UnitTests.TestHelpers; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest; [TestFixture] -public class ManifestContentAppTests +public class LegacyManifestContentAppTests { [Test] public void Test() @@ -70,11 +70,11 @@ public class ManifestContentAppTests private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) { - var definition = JsonConvert.DeserializeObject("{" + + var definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? string.Empty : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); - var factory = new ManifestContentAppFactory(definition, TestHelper.IOHelper); + var factory = new LegacyManifestContentAppFactory(definition, TestHelper.IOHelper); var app = factory.GetContentAppFor(source, groups); if (expected) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestParserTests.cs similarity index 96% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestParserTests.cs index 2cb1341f0a..f8f1f49610 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestParserTests.cs @@ -24,7 +24,7 @@ using Umbraco.Cms.Tests.UnitTests.TestHelpers; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest; [TestFixture] -public class ManifestParserTests +public class LegacyManifestParserTests { [SetUp] public void Setup() @@ -37,21 +37,21 @@ public class ManifestParserTests }; _ioHelper = TestHelper.IOHelper; var loggerFactory = NullLoggerFactory.Instance; - _parser = new ManifestParser( + _parser = new LegacyManifestParser( AppCaches.Disabled, new ManifestValueValidatorCollection(() => validators), - new ManifestFilterCollection(() => Enumerable.Empty()), - loggerFactory.CreateLogger(), + new LegacyManifestFilterCollection(() => Enumerable.Empty()), + loggerFactory.CreateLogger(), _ioHelper, TestHelper.GetHostingEnvironment(), new JsonNetSerializer(), Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of()); } - private ManifestParser _parser; + private LegacyManifestParser _parser; private IIOHelper _ioHelper; [Test] @@ -410,14 +410,14 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 var manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.ContentApps.Length); - Assert.IsInstanceOf(manifest.ContentApps[0]); + Assert.IsInstanceOf(manifest.ContentApps[0]); var app0 = manifest.ContentApps[0]; Assert.AreEqual("myPackageApp1", app0.Alias); Assert.AreEqual("My App1", app0.Name); Assert.AreEqual("icon-foo", app0.Icon); Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/ContentApps/MyApp1.html"), app0.View); - Assert.IsInstanceOf(manifest.ContentApps[1]); + Assert.IsInstanceOf(manifest.ContentApps[1]); var app1 = manifest.ContentApps[1]; Assert.AreEqual("myPackageApp2", app1.Alias); Assert.AreEqual("My App2", app1.Name); @@ -447,7 +447,7 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 var manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.Dashboards.Length); - Assert.IsInstanceOf(manifest.Dashboards[0]); + Assert.IsInstanceOf(manifest.Dashboards[0]); var db0 = manifest.Dashboards[0]; Assert.AreEqual("something", db0.Alias); Assert.AreEqual(100, db0.Weight); @@ -460,7 +460,7 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual(AccessRuleType.Deny, db0.AccessRules[1].Type); Assert.AreEqual("foo", db0.AccessRules[1].Value); - Assert.IsInstanceOf(manifest.Dashboards[1]); + Assert.IsInstanceOf(manifest.Dashboards[1]); var db1 = manifest.Dashboards[1]; Assert.AreEqual("something.else", db1.Alias); Assert.AreEqual(-1, db1.Weight); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestReaderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestReaderTests.cs new file mode 100644 index 0000000000..a4b2586e82 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestReaderTests.cs @@ -0,0 +1,244 @@ +using System.Text; +using System.Text.Json; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Infrastructure.Manifest; +using Umbraco.Cms.Infrastructure.Serialization; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest; + +[TestFixture] +public class PackageManifestReaderTests +{ + private IPackageManifestReader _reader; + private Mock _rootDirectoryContentsMock; + private Mock> _loggerMock; + private Mock _fileProviderMock; + + [SetUp] + public void SetUp() + { + _rootDirectoryContentsMock = new Mock(); + _fileProviderMock = new Mock(); + _fileProviderMock + .Setup(m => m.GetDirectoryContents(Constants.SystemDirectories.AppPlugins)) + .Returns(_rootDirectoryContentsMock.Object); + var fileProviderFactoryMock = new Mock(); + fileProviderFactoryMock.Setup(m => m.Create()).Returns(_fileProviderMock.Object); + + _loggerMock = new Mock>(); + _reader = new AppPluginsFileProviderPackageManifestReader(fileProviderFactoryMock.Object, new SystemTextJsonSerializer(), _loggerMock.Object); + } + + [Test] + public async Task Can_Read_PackageManifests_At_Root() + { + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { CreatePackageManifestFile() }.GetEnumerator()); + + var result = await _reader.ReadPackageManifestsAsync(); + Assert.AreEqual(1, result.Count()); + + var first = result.First(); + Assert.AreEqual("My Package", first.Name); + Assert.AreEqual("1.2.3", first.Version); + Assert.AreEqual(2, first.Extensions.Count()); + Assert.IsTrue(first.Extensions.All(e => e is JsonElement)); + } + + [Test] + public async Task Can_Read_PackageManifest_In_Root_Directories() + { + var directoryOne = CreateDirectoryMock("/my-extension", CreatePackageManifestFile(DefaultPackageManifestContent("Package One"))); + var directoryTwo = CreateDirectoryMock("/my-other-extension", CreatePackageManifestFile(DefaultPackageManifestContent("Package Two"))); + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { directoryOne, directoryTwo }.GetEnumerator()); + + var result = await _reader.ReadPackageManifestsAsync(); + Assert.AreEqual(2, result.Count()); + Assert.AreEqual("Package One", result.First().Name); + Assert.AreEqual("Package Two", result.Last().Name); + } + + [Test] + public async Task Can_Read_PackageManifests_Recursively() + { + var childFolder = CreateDirectoryMock("/my-parent-folder/my-child-folder", CreatePackageManifestFile(DefaultPackageManifestContent("Nested Package"))); + var parentFolder = CreateDirectoryMock("/my-parent-folder", childFolder); + + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { parentFolder }.GetEnumerator()); + + var result = await _reader.ReadPackageManifestsAsync(); + Assert.AreEqual(1, result.Count()); + Assert.AreEqual("Nested Package", result.First().Name); + } + + [Test] + public async Task Can_Skip_Empty_Directories() + { + var packageFolder = CreateDirectoryMock("/my-package-folder", CreatePackageManifestFile(DefaultPackageManifestContent("My Package"))); + var emptyFolder = CreateDirectoryMock("/my-empty-folder"); + + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { emptyFolder, packageFolder }.GetEnumerator()); + + var result = await _reader.ReadPackageManifestsAsync(); + Assert.AreEqual(1, result.Count()); + Assert.AreEqual("My Package", result.First().Name); + } + + [Test] + public async Task Can_Skip_Other_Files() + { + var packageFolder = CreateDirectoryMock( + "/my-package-folder", + CreateOtherFile("my.js"), + CreatePackageManifestFile(DefaultPackageManifestContent("My Package"))); + var otherFolder = CreateDirectoryMock( + "/my-empty-folder", + CreateOtherFile("some.js"), + CreateOtherFile("some.css")); + + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { otherFolder, packageFolder }.GetEnumerator()); + + var result = await _reader.ReadPackageManifestsAsync(); + Assert.AreEqual(1, result.Count()); + Assert.AreEqual("My Package", result.First().Name); + } + + [Test] + public async Task Can_Handle_All_Empty_Directories() + { + var folders = Enumerable.Range(1, 10).Select(i => CreateDirectoryMock($"/my-empty-folder-{i}")).ToList(); + + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(folders.GetEnumerator()); + + var result = await _reader.ReadPackageManifestsAsync(); + Assert.AreEqual(0, result.Count()); + } + + [Test] + public async Task Cannot_Read_PackageManifest_Without_Name() + { + var content = @"{ + ""version"": ""1.2.3"", + ""allowTelemetry"": true, + ""extensions"": [{ + ""type"": ""tree"" + }, { + ""type"": ""headerApp"" + } + ] +}"; + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { CreatePackageManifestFile(content) }.GetEnumerator()); + + Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); + EnsureLogErrorWasCalled(); + } + + [Test] + public async Task Cannot_Read_PackageManifest_Without_Extensions() + { + var content = @"{ + ""name"": ""Something"", + ""version"": ""1.2.3"", + ""allowTelemetry"": true +}"; + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { CreatePackageManifestFile(content) }.GetEnumerator()); + + Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); + EnsureLogErrorWasCalled(); + } + + [TestCase("This is not JSON")] + [TestCase(@"{""name"": ""invalid-json"", ""version"": ")] + public async Task Cannot_Read_Invalid_PackageManifest(string content) + { + _rootDirectoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(new List { CreatePackageManifestFile(content) }.GetEnumerator()); + + Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); + EnsureLogErrorWasCalled(); + } + + private void EnsureLogErrorWasCalled(int numberOfTimes = 1) => + _loggerMock.Verify( + x => x.Log( + It.Is(l => l == LogLevel.Error), + It.IsAny(), + It.Is((v, t) => true), + It.IsAny(), + It.Is>((v, t) => true)), + Times.Exactly(numberOfTimes)); + + private IFileInfo CreateDirectoryMock(string path, params IFileInfo[] children) + { + var directoryContentsMock = new Mock(); + directoryContentsMock + .Setup(f => f.GetEnumerator()) + .Returns(children.ToList().GetEnumerator()); + + _fileProviderMock + .Setup(m => m.GetDirectoryContents($"{Constants.SystemDirectories.AppPlugins}{path}")) + .Returns(directoryContentsMock.Object); + + var fileInfo = new Mock(); + fileInfo.SetupGet(f => f.IsDirectory).Returns(true); + fileInfo.SetupGet(f => f.Name).Returns(path.Split(Constants.CharArrays.ForwardSlash).Last()); + + return fileInfo.Object; + } + + private IFileInfo CreatePackageManifestFile(string? content = null) + { + content ??= DefaultPackageManifestContent(); + + var fileInfo = new Mock(); + fileInfo.SetupGet(f => f.IsDirectory).Returns(false); + fileInfo.SetupGet(f => f.Name).Returns("umbraco-package.json"); + fileInfo.Setup(f => f.CreateReadStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(content))); + + return fileInfo.Object; + } + + private IFileInfo CreateOtherFile(string name) + { + var fileInfo = new Mock(); + fileInfo.SetupGet(f => f.IsDirectory).Returns(false); + fileInfo.SetupGet(f => f.Name).Returns(name); + fileInfo.Setup(f => f.CreateReadStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("this is some file content"))); + + return fileInfo.Object; + } + + private static string DefaultPackageManifestContent(string name = "My Package") + => @"{ + ""name"": ""##NAME##"", + ""version"": ""1.2.3"", + ""allowTelemetry"": true, + ""extensions"": [{ + ""type"": ""tree"" + }, { + ""type"": ""headerApp"" + } + ] +}".Replace("##NAME##", name); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs new file mode 100644 index 0000000000..419e1e10dd --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Infrastructure.Manifest; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest; + +[TestFixture] +public class PackageManifestServiceTests +{ + private IPackageManifestService _service; + private Mock _readerMock; + private IAppPolicyCache _runtimeCache; + + [SetUp] + public void SetUp() + { + _readerMock = new Mock(); + _readerMock.Setup(r => r.ReadPackageManifestsAsync()).ReturnsAsync( + new[] + { + new PackageManifest { Name = "Test", Extensions = Array.Empty() } + }); + + _runtimeCache = new ObjectCacheAppCache(); + AppCaches appCaches = new AppCaches( + _runtimeCache, + NoAppCache.Instance, + new IsolatedCaches(type => NoAppCache.Instance)); + + _service = new PackageManifestService(new[] { _readerMock.Object }, appCaches, new OptionsWrapper(new PackageManifestSettings())); + } + + [Test] + public async Task Caches_PackageManifests() + { + var result = await _service.GetPackageManifestsAsync(); + Assert.AreEqual(1, result.Count()); + + var result2 = await _service.GetPackageManifestsAsync(); + Assert.AreEqual(1, result2.Count()); + + var result3 = await _service.GetPackageManifestsAsync(); + Assert.AreEqual(1, result3.Count()); + + _readerMock.Verify(r => r.ReadPackageManifestsAsync(), Times.Exactly(1)); + } + + [Test] + public async Task Reloads_PackageManifest_After_Cache_Clear() + { + var result = await _service.GetPackageManifestsAsync(); + Assert.AreEqual(1, result.Count()); + _runtimeCache.Clear(); + + var result2 = await _service.GetPackageManifestsAsync(); + Assert.AreEqual(1, result2.Count()); + _runtimeCache.Clear(); + + var result3 = await _service.GetPackageManifestsAsync(); + Assert.AreEqual(1, result3.Count()); + _runtimeCache.Clear(); + + _readerMock.Verify(r => r.ReadPackageManifestsAsync(), Times.Exactly(3)); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs index 83aa368d70..c29590cf0d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; @@ -16,62 +13,60 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry; public class TelemetryServiceTests { [Test] - public void UsesGetOrCreateSiteId() + public async Task UsesGetOrCreateSiteId() { var version = CreateUmbracoVersion(9, 3, 1); var siteIdentifierServiceMock = new Mock(); var usageInformationServiceMock = new Mock(); var sut = new TelemetryService( - Mock.Of(), version, siteIdentifierServiceMock.Object, usageInformationServiceMock.Object, - Mock.Of()); + Mock.Of(), + Mock.Of()); Guid guid; - sut.TryGetTelemetryReportData(out _); + await sut.GetTelemetryReportDataAsync(); siteIdentifierServiceMock.Verify(x => x.TryGetOrCreateSiteIdentifier(out guid), Times.Once); } [Test] - public void SkipsIfCantGetOrCreateId() + public async Task SkipsIfCantGetOrCreateId() { var version = CreateUmbracoVersion(9, 3, 1); var sut = new TelemetryService( - Mock.Of(), version, CreateSiteIdentifierService(false), Mock.Of(), - Mock.Of()); + Mock.Of(), + Mock.Of()); - var result = sut.TryGetTelemetryReportData(out var telemetry); - - Assert.IsFalse(result); - Assert.IsNull(telemetry); + var result = await sut.GetTelemetryReportDataAsync(); + Assert.IsNull(result); } [Test] - public void ReturnsSemanticVersionWithoutBuild() + public async Task ReturnsSemanticVersionWithoutBuild() { var version = CreateUmbracoVersion(9, 1, 1, "-rc", "-ad2f4k2d"); var metricsConsentService = new Mock(); metricsConsentService.Setup(x => x.GetConsentLevel()).Returns(TelemetryLevel.Detailed); var sut = new TelemetryService( - Mock.Of(), version, CreateSiteIdentifierService(), Mock.Of(), - metricsConsentService.Object); + metricsConsentService.Object, + Mock.Of()); - var result = sut.TryGetTelemetryReportData(out var telemetry); + var result = await sut.GetTelemetryReportDataAsync(); - Assert.IsTrue(result); - Assert.AreEqual("9.1.1-rc", telemetry.Version); + Assert.IsNotNull(result); + Assert.AreEqual("9.1.1-rc", result.Version); } [Test] - public void CanGatherPackageTelemetry() + public async Task CanGatherPackageTelemetry() { var version = CreateUmbracoVersion(9, 1, 1); var versionPackageName = "VersionPackage"; @@ -79,69 +74,69 @@ public class TelemetryServiceTests var noVersionPackageName = "NoVersionPackage"; PackageManifest[] manifests = { - new() { PackageName = versionPackageName, Version = packageVersion }, - new() { PackageName = noVersionPackageName }, + new() { Name = versionPackageName, Version = packageVersion, Extensions = Array.Empty()}, + new() { Name = noVersionPackageName, Extensions = Array.Empty() }, }; - var manifestParser = CreateManifestParser(manifests); + var packageManifestService = CreatePackageManifestService(manifests); var metricsConsentService = new Mock(); metricsConsentService.Setup(x => x.GetConsentLevel()).Returns(TelemetryLevel.Basic); var sut = new TelemetryService( - manifestParser, version, CreateSiteIdentifierService(), Mock.Of(), - metricsConsentService.Object); + metricsConsentService.Object, + packageManifestService); - var success = sut.TryGetTelemetryReportData(out var telemetry); + var result = await sut.GetTelemetryReportDataAsync(); - Assert.IsTrue(success); + Assert.IsNotNull(result); Assert.Multiple(() => { - Assert.AreEqual(2, telemetry.Packages.Count()); - var versionPackage = telemetry.Packages.FirstOrDefault(x => x.Name == versionPackageName); + Assert.AreEqual(2, result.Packages.Count()); + var versionPackage = result.Packages.FirstOrDefault(x => x.Name == versionPackageName); Assert.AreEqual(versionPackageName, versionPackage.Name); Assert.AreEqual(packageVersion, versionPackage.Version); - var noVersionPackage = telemetry.Packages.FirstOrDefault(x => x.Name == noVersionPackageName); + var noVersionPackage = result.Packages.FirstOrDefault(x => x.Name == noVersionPackageName); Assert.AreEqual(noVersionPackageName, noVersionPackage.Name); Assert.AreEqual(string.Empty, noVersionPackage.Version); }); } [Test] - public void RespectsAllowPackageTelemetry() + public async Task RespectsAllowPackageTelemetry() { var version = CreateUmbracoVersion(9, 1, 1); PackageManifest[] manifests = { - new() { PackageName = "DoNotTrack", AllowPackageTelemetry = false }, - new() { PackageName = "TrackingAllowed", AllowPackageTelemetry = true }, + new() { Name = "DoNotTrack", AllowTelemetry = false, Extensions = Array.Empty() }, + new() { Name = "TrackingAllowed", AllowTelemetry = true, Extensions = Array.Empty() }, }; - var manifestParser = CreateManifestParser(manifests); + var packageManifestService = CreatePackageManifestService(manifests); var metricsConsentService = new Mock(); metricsConsentService.Setup(x => x.GetConsentLevel()).Returns(TelemetryLevel.Basic); var sut = new TelemetryService( - manifestParser, version, CreateSiteIdentifierService(), Mock.Of(), - metricsConsentService.Object); + metricsConsentService.Object, + packageManifestService); - var success = sut.TryGetTelemetryReportData(out var telemetry); + var result = await sut.GetTelemetryReportDataAsync(); - Assert.IsTrue(success); + Assert.IsNotNull(result); Assert.Multiple(() => { - Assert.AreEqual(1, telemetry.Packages.Count()); - Assert.AreEqual("TrackingAllowed", telemetry.Packages.First().Name); + Assert.AreEqual(1, result.Packages.Count()); + Assert.AreEqual("TrackingAllowed", result.Packages.First().Name); }); } - private IManifestParser CreateManifestParser(IEnumerable manifests) + private IPackageManifestService CreatePackageManifestService(IEnumerable manifests) { - var manifestParserMock = new Mock(); - manifestParserMock.Setup(x => x.GetManifests()).Returns(manifests); - return manifestParserMock.Object; + var mock = new Mock(); + mock.Setup(x => x.GetPackageManifestsAsync()).Returns(Task.FromResult(manifests)); + return mock.Object; } private IUmbracoVersion CreateUmbracoVersion(int major, int minor, int patch, string prerelease = "", string build = "")