From 588525e99fc4ee786ec5f7f6d283a4ff6e0d5077 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 1 Jun 2022 14:46:01 +0200 Subject: [PATCH 01/27] Add Umbraco specific global usings --- src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props | 4 ++++ src/Umbraco.Web.UI/Startup.cs | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props b/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props index ea0b013665..7266a05f6e 100644 --- a/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props +++ b/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props @@ -5,4 +5,8 @@ $(DefaultItemExcludes);umbraco/Logs/** $(DefaultItemExcludes);wwwroot/media/** + + + + diff --git a/src/Umbraco.Web.UI/Startup.cs b/src/Umbraco.Web.UI/Startup.cs index 0176974b6b..eba72b6924 100644 --- a/src/Umbraco.Web.UI/Startup.cs +++ b/src/Umbraco.Web.UI/Startup.cs @@ -1,12 +1,3 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Extensions; - namespace Umbraco.Cms.Web.UI { public class Startup From 87dbc8b2874efbdd201d34e117ebb3be9ff31719 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 1 Jun 2022 15:26:55 +0200 Subject: [PATCH 02/27] Enable implicit usings --- src/Umbraco.Web.UI/Program.cs | 2 - .../UmbracoProject/UmbracoProject.csproj | 59 +++++++++---------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI/Program.cs b/src/Umbraco.Web.UI/Program.cs index 44c045800d..fb6c724b3f 100644 --- a/src/Umbraco.Web.UI/Program.cs +++ b/src/Umbraco.Web.UI/Program.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; using Umbraco.Cms.Web.Common.Hosting; namespace Umbraco.Cms.Web.UI diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj index e34e743acb..efaa0edbb0 100644 --- a/templates/UmbracoProject/UmbracoProject.csproj +++ b/templates/UmbracoProject/UmbracoProject.csproj @@ -1,38 +1,37 @@ + + net6.0 + enable + enable + Umbraco.Cms.Web.UI + - - net6.0 - enable - Umbraco.Cms.Web.UI - + + + - - - + + + + + - - - - - + + + + - - - - + + true + - - true - - - - - - - - - false - false - + + + + + + false + false + From 4dcb017afe68477d77b77fafb6f1b7df91210a14 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 7 Jun 2022 08:44:03 +0200 Subject: [PATCH 03/27] v10: Wait for updated ConnectionStrings during install (#12536) * Do not change/reload configuration * Wait for updated connection string options --- .../Configuration/JsonConfigManipulator.cs | 30 ++++--------------- .../Migrations/Install/DatabaseBuilder.cs | 20 +++++++++++++ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs index e9e1236a9e..6d583151ba 100644 --- a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs +++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs @@ -35,11 +35,6 @@ namespace Umbraco.Cms.Core.Configuration public void RemoveConnectionString() { - // Update and reload configuration - _configuration[UmbracoConnectionStringPath] = null; - _configuration[UmbracoConnectionStringProviderNamePath] = null; - (_configuration as IConfigurationRoot)?.Reload(); - // Remove keys from JSON var provider = GetJsonConfigurationProvider(UmbracoConnectionStringPath); @@ -58,11 +53,6 @@ namespace Umbraco.Cms.Core.Configuration public void SaveConnectionString(string connectionString, string? providerName) { - // Update and reload configuration - _configuration[UmbracoConnectionStringPath] = connectionString; - _configuration[UmbracoConnectionStringProviderNamePath] = providerName; - (_configuration as IConfigurationRoot)?.Reload(); - // Save keys to JSON var provider = GetJsonConfigurationProvider(); @@ -84,10 +74,6 @@ namespace Umbraco.Cms.Core.Configuration public void SaveConfigValue(string key, object value) { - // Update and reload configuration - _configuration[key] = value?.ToString(); - (_configuration as IConfigurationRoot)?.Reload(); - // Save key to JSON var provider = GetJsonConfigurationProvider(); @@ -122,10 +108,6 @@ namespace Umbraco.Cms.Core.Configuration public void SaveDisableRedirectUrlTracking(bool disable) { - // Update and reload configuration - _configuration["Umbraco:CMS:WebRouting:DisableRedirectUrlTracking"] = disable.ToString(); - (_configuration as IConfigurationRoot)?.Reload(); - // Save key to JSON var provider = GetJsonConfigurationProvider(); @@ -147,10 +129,6 @@ namespace Umbraco.Cms.Core.Configuration public void SetGlobalId(string id) { - // Update and reload configuration - _configuration["Umbraco:CMS:Global:Id"] = id; - (_configuration as IConfigurationRoot)?.Reload(); - // Save key to JSON var provider = GetJsonConfigurationProvider(); @@ -336,17 +314,21 @@ namespace Umbraco.Cms.Core.Configuration { if (token is JObject obj) { - foreach (var property in obj.Properties()) { if (name is null) + { return property.Value; + } + if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase)) + { return property.Value; + } } } + return null; } - } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index 2f8bd3f4ec..b3188720b0 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -163,7 +163,27 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install if (!isTrialRun) { + // File configuration providers use a delay before reloading and triggering changes, so wait + using var isChanged = new ManualResetEvent(false); + using IDisposable? onChange = _connectionStrings.OnChange((options, name) => + { + // Only watch default named option (CurrentValue) + if (name != Options.DefaultName) + { + return; + } + + // Signal change + isChanged.Set(); + }); + + // Update configuration and wait for change _configManipulator.SaveConnectionString(connectionString, providerName); + if (!isChanged.WaitOne(10_000)) + { + throw new InstallException("Didn't retrieve updated connection string within 10 seconds, try manual configuration instead."); + } + Configure(_globalSettings.CurrentValue.InstallMissingDatabase || providerMeta.ForceCreateDatabase); } From 559a85812f9b0f893e76cbd893bda8ab4386b903 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 08:59:45 +0200 Subject: [PATCH 04/27] recase assigndomain (#12448) --- src/Umbraco.Core/Actions/ActionAssignDomain.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Actions/ActionAssignDomain.cs b/src/Umbraco.Core/Actions/ActionAssignDomain.cs index 0638f605af..452ca51549 100644 --- a/src/Umbraco.Core/Actions/ActionAssignDomain.cs +++ b/src/Umbraco.Core/Actions/ActionAssignDomain.cs @@ -17,7 +17,8 @@ public class ActionAssignDomain : IAction public char Letter => ActionLetter; /// - public string Alias => "assignDomain"; + // This is all lower-case because of case sensitive filesystems, see issue: https://github.com/umbraco/Umbraco-CMS/issues/11670 + public string Alias => "assigndomain"; /// public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; From ea9d27c03881c04a952aaa0aa07e05185f4cbe7e Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 7 Jun 2022 10:25:05 +0100 Subject: [PATCH 05/27] Add depth property to ICoreScope (#12540) --- src/Umbraco.Core/Scoping/ICoreScope.cs | 9 +++++- src/Umbraco.Infrastructure/Scoping/Scope.cs | 13 +++++++++ .../Scoping/ScopeUnitTests.cs | 29 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Scoping/ICoreScope.cs b/src/Umbraco.Core/Scoping/ICoreScope.cs index 8bb85ca29d..e1382f0ec8 100644 --- a/src/Umbraco.Core/Scoping/ICoreScope.cs +++ b/src/Umbraco.Core/Scoping/ICoreScope.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; @@ -9,6 +8,14 @@ namespace Umbraco.Cms.Core.Scoping; /// public interface ICoreScope : IDisposable, IInstanceIdentifiable { + /// + /// Gets the distance from the root scope. + /// + /// + /// A zero represents a root scope, any value greater than zero represents a child scope. + /// + public int Depth => -1; + /// /// Gets the scope notification publisher /// diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 190261a808..e1ad1d2389 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -478,6 +478,19 @@ namespace Umbraco.Cms.Infrastructure.Scoping } } + public int Depth + { + get + { + if (ParentScope == null) + { + return 0; + } + + return ParentScope.Depth + 1; + } + } + public IScopedNotificationPublisher Notifications { get diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs index f429f584e5..5734622e3e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs @@ -579,5 +579,34 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Scoping Assert.IsNull(realScope.GetReadLocks()); } } + + [Test] + public void Depth_WhenRootScope_ReturnsZero() + { + var scopeProvider = GetScopeProvider(out var syntaxProviderMock); + + using (var scope = scopeProvider.CreateScope()) + { + Assert.AreEqual(0,scope.Depth); + } + } + + + [Test] + public void Depth_WhenChildScope_ReturnsDepth() + { + var scopeProvider = GetScopeProvider(out var syntaxProviderMock); + + using (scopeProvider.CreateScope()) + { + using (scopeProvider.CreateScope()) + { + using (var c2 = scopeProvider.CreateScope()) + { + Assert.AreEqual(2, c2.Depth); + } + } + } + } } } From fde1b6685a98e175c03035883e5e5839963beb71 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 7 Jun 2022 10:35:36 +0100 Subject: [PATCH 06/27] Remove ambient scope stack from httpcontext.items. (#12539) This change makes it easier to use service calls in parallel whilst a httpcontext is available. --- .../UmbracoBuilder.CoreServices.cs | 7 +- .../Scoping/AmbientScopeContextStack.cs | 39 ++ .../Scoping/AmbientScopeStack.cs | 39 ++ .../Scoping/IAmbientScopeContextStack.cs | 10 + .../Scoping/IAmbientScopeStack.cs | 7 + src/Umbraco.Infrastructure/Scoping/Scope.cs | 23 +- .../Scoping/ScopeProvider.cs | 358 +++--------------- .../Scoping/ScopeTests.cs | 50 --- .../Services/ThreadSafetyServiceTest.cs | 10 - .../Umbraco.Core/Components/ComponentTests.cs | 2 +- .../ScopedNotificationPublisherTests.cs | 3 +- .../Scoping/ScopeUnitTests.cs | 3 +- 12 files changed, 164 insertions(+), 387 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs create mode 100644 src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs create mode 100644 src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs create mode 100644 src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 4b30a10159..48bdb6d399 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -95,11 +95,14 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Mappers()?.AddCoreMappers(); // register the scope provider - builder.Services.AddSingleton(); // implements IScopeProvider, IScopeAccessor + builder.Services.AddSingleton(sp => ActivatorUtilities.CreateInstance(sp, sp.GetRequiredService())); // implements IScopeProvider, IScopeAccessor builder.Services.AddSingleton(f => f.GetRequiredService()); builder.Services.AddSingleton(f => f.GetRequiredService()); builder.Services.AddSingleton(f => f.GetRequiredService()); - builder.Services.AddSingleton(f => f.GetRequiredService()); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton(f => f.GetRequiredService()); + builder.Services.AddSingleton(); builder.Services.AddScoped(); diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs new file mode 100644 index 0000000000..18e68120a6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs @@ -0,0 +1,39 @@ +using System.Collections.Concurrent; +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Infrastructure.Scoping; + +internal class AmbientScopeContextStack : IAmbientScopeContextStack +{ + private static AsyncLocal> _stack = new(); + + public IScopeContext? AmbientContext + { + get + { + if (_stack.Value?.TryPeek(out IScopeContext? ambientContext) ?? false) + { + return ambientContext; + } + + return null; + } + } + + public IScopeContext Pop() + { + if (_stack.Value?.TryPop(out IScopeContext? ambientContext) ?? false) + { + return ambientContext; + } + + throw new InvalidOperationException("No AmbientContext was found."); + } + + public void Push(IScopeContext scope) + { + _stack.Value ??= new ConcurrentStack(); + + _stack.Value.Push(scope); + } +} diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs new file mode 100644 index 0000000000..3ad5e89e51 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs @@ -0,0 +1,39 @@ +using System.Collections.Concurrent; + +namespace Umbraco.Cms.Infrastructure.Scoping +{ + internal class AmbientScopeStack : IAmbientScopeStack + { + private static AsyncLocal> _stack = new (); + + public IScope? AmbientScope + { + get + { + if (_stack.Value?.TryPeek(out IScope? ambientScope) ?? false) + { + return ambientScope; + } + + return null; + } + } + + public IScope Pop() + { + if (_stack.Value?.TryPop(out IScope? ambientScope) ?? false) + { + return ambientScope; + } + + throw new InvalidOperationException("No AmbientScope was found."); + } + + public void Push(IScope scope) + { + _stack.Value ??= new ConcurrentStack(); + + _stack.Value.Push(scope); + } + } +} diff --git a/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs new file mode 100644 index 0000000000..28da9a6427 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Infrastructure.Scoping; + +internal interface IAmbientScopeContextStack +{ + IScopeContext? AmbientContext { get; } + IScopeContext Pop(); + void Push(IScopeContext scope); +} diff --git a/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs new file mode 100644 index 0000000000..71cfbf3a03 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Infrastructure.Scoping; + +internal interface IAmbientScopeStack : IScopeAccessor +{ + IScope Pop(); + void Push(IScope scope); +} diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index e1ad1d2389..2fdc1a04dd 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -40,7 +40,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping private readonly bool? _scopeFileSystem; private readonly ScopeProvider _scopeProvider; - private bool _callContext; private bool? _completed; private IUmbracoDatabase? _database; @@ -93,7 +92,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping _eventDispatcher = eventDispatcher; _notificationPublisher = notificationPublisher; _scopeFileSystem = scopeFileSystems; - _callContext = callContext; _autoComplete = autoComplete; Detachable = detachable; _dictionaryLocker = new object(); @@ -259,24 +257,15 @@ namespace Umbraco.Cms.Infrastructure.Scoping { } + [Obsolete("Scopes are never stored on HttpContext.Items anymore, so CallContext is always true.")] // a value indicating whether to force call-context public bool CallContext { - get + get => true; + set { - if (_callContext) - { - return true; - } - - if (ParentScope != null) - { - return ParentScope.CallContext; - } - - return false; + // NOOP - always true. } - set => _callContext = value; } public bool ScopedFileSystems @@ -564,7 +553,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping } } - _scopeProvider.PopAmbientScope(this); // might be null = this is how scopes are removed from context objects + _scopeProvider.PopAmbientScope(); // might be null = this is how scopes are removed from context objects #if DEBUG_SCOPES _scopeProvider.Disposed(this); @@ -916,7 +905,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping // by Deploy which I don't fully understand since there is limited tests on this in the CMS if (OrigScope != _scopeProvider.AmbientScope) { - _scopeProvider.PopAmbientScope(_scopeProvider.AmbientScope); + _scopeProvider.PopAmbientScope(); } if (OrigContext != _scopeProvider.AmbientContext) diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 4022f366e2..9be7eee387 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -1,5 +1,5 @@ -using System; using System.Data; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; @@ -8,10 +8,9 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Infrastructure.Persistence; using CoreDebugSettings = Umbraco.Cms.Core.Configuration.Models.CoreDebugSettings; using Umbraco.Extensions; -using System.Collections.Concurrent; -using System.Threading; using Umbraco.Cms.Core.DistributedLocking; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Web.Common.DependencyInjection; #if DEBUG_SCOPES using System.Linq; @@ -27,20 +26,46 @@ namespace Umbraco.Cms.Infrastructure.Scoping ICoreScopeProvider, IScopeProvider, Core.Scoping.IScopeProvider, - IScopeAccessor + IScopeAccessor // TODO: No need to implement this here but literally hundreds of our tests cast ScopeProvider to ScopeAccessor { private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly IRequestCache _requestCache; + private readonly IEventAggregator _eventAggregator; + private readonly IAmbientScopeStack _ambientScopeStack; + private readonly IAmbientScopeContextStack _ambientContextStack; + private readonly FileSystems _fileSystems; private CoreDebugSettings _coreDebugSettings; private readonly MediaFileManager _mediaFileManager; - private static readonly AsyncLocal> s_scopeStack = new AsyncLocal>(); - private static readonly AsyncLocal> s_scopeContextStack = new AsyncLocal>(); - private static readonly string s_scopeItemKey = typeof(Scope).FullName!; - private static readonly string s_contextItemKey = typeof(ScopeProvider).FullName!; - private readonly IEventAggregator _eventAggregator; + public ScopeProvider( + IAmbientScopeStack ambientScopeStack, + IAmbientScopeContextStack ambientContextStack, + IDistributedLockingMechanismFactory distributedLockingMechanismFactory, + IUmbracoDatabaseFactory databaseFactory, + FileSystems fileSystems, + IOptionsMonitor coreDebugSettings, + MediaFileManager mediaFileManager, + ILoggerFactory loggerFactory, + IEventAggregator eventAggregator) + { + DistributedLockingMechanismFactory = distributedLockingMechanismFactory; + DatabaseFactory = databaseFactory; + _ambientScopeStack = ambientScopeStack; + _ambientContextStack = ambientContextStack; + _fileSystems = fileSystems; + _coreDebugSettings = coreDebugSettings.CurrentValue; + _mediaFileManager = mediaFileManager; + _logger = loggerFactory.CreateLogger(); + _loggerFactory = loggerFactory; + _eventAggregator = eventAggregator; + // take control of the FileSystems + _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; + + coreDebugSettings.OnChange(x => _coreDebugSettings = x); + } + + [Obsolete("Please use an alternative constructor. This constructor is due for removal in v12.")] public ScopeProvider( IDistributedLockingMechanismFactory distributedLockingMechanismFactory, IUmbracoDatabaseFactory databaseFactory, @@ -50,20 +75,17 @@ namespace Umbraco.Cms.Infrastructure.Scoping ILoggerFactory loggerFactory, IRequestCache requestCache, IEventAggregator eventAggregator) + : this( + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + distributedLockingMechanismFactory, + databaseFactory, + fileSystems, + coreDebugSettings, + mediaFileManager, + loggerFactory, + eventAggregator) { - DistributedLockingMechanismFactory = distributedLockingMechanismFactory; - DatabaseFactory = databaseFactory; - _fileSystems = fileSystems; - _coreDebugSettings = coreDebugSettings.CurrentValue; - _mediaFileManager = mediaFileManager; - _logger = loggerFactory.CreateLogger(); - _loggerFactory = loggerFactory; - _requestCache = requestCache; - _eventAggregator = eventAggregator; - // take control of the FileSystems - _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; - - coreDebugSettings.OnChange(x => _coreDebugSettings = x); } public IDistributedLockingMechanismFactory DistributedLockingMechanismFactory { get; } @@ -72,195 +94,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping public ISqlContext SqlContext => DatabaseFactory.SqlContext; - #region Context - - private void MoveHttpContextScopeToCallContext() - { - var source = (ConcurrentStack?)_requestCache.Get(s_scopeItemKey); - ConcurrentStack? stack = s_scopeStack.Value; - MoveContexts(s_scopeItemKey, source, stack, (_, v) => s_scopeStack.Value = v); - } - - private void MoveHttpContextScopeContextToCallContext() - { - var source = (ConcurrentStack?)_requestCache.Get(s_contextItemKey); - ConcurrentStack? stack = s_scopeContextStack.Value; - MoveContexts(s_contextItemKey, source, stack, (_, v) => s_scopeContextStack.Value = v); - } - - private void MoveCallContextScopeToHttpContext() - { - ConcurrentStack? source = s_scopeStack.Value; - var stack = (ConcurrentStack?)_requestCache.Get(s_scopeItemKey); - MoveContexts(s_scopeItemKey, source, stack, (k, v) => _requestCache.Set(k, v)); - } - - private void MoveCallContextScopeContextToHttpContext() - { - ConcurrentStack? source = s_scopeContextStack.Value; - var stack = (ConcurrentStack?)_requestCache.Get(s_contextItemKey); - MoveContexts(s_contextItemKey, source, stack, (k, v) => _requestCache.Set(k, v)); - } - - private void MoveContexts(string key, ConcurrentStack? source, ConcurrentStack? stack, Action> setter) - where T : class, IInstanceIdentifiable - { - if (source == null) - { - return; - } - - if (stack != null) - { - stack.Clear(); - } - else - { - // TODO: This isn't going to copy it back up the execution context chain - stack = new ConcurrentStack(); - setter(key, stack); - } - - var arr = new T[source.Count]; - source.CopyTo(arr, 0); - Array.Reverse(arr); - foreach (T a in arr) - { - stack.Push(a); - } - - source.Clear(); - } - - private void SetCallContextScope(IScope? value) - { - ConcurrentStack? stack = s_scopeStack.Value; - -#if DEBUG_SCOPES - // first, null-register the existing value - if (stack != null && stack.TryPeek(out IScope ambientScope)) - { - RegisterContext(ambientScope, null); - } - - // then register the new value - if (value != null) - { - RegisterContext(value, "call"); - } -#endif - - if (value == null) - { - if (stack != null) - { - stack.TryPop(out _); - } - } - else - { - -#if DEBUG_SCOPES - _logger.LogDebug("AddObject " + value.InstanceId.ToString("N").Substring(0, 8)); -#endif - if (stack == null) - { - stack = new ConcurrentStack(); - } - stack.Push(value); - s_scopeStack.Value = stack; - } - } - - private void SetCallContextScopeContext(IScopeContext? value) - { - ConcurrentStack? stack = s_scopeContextStack.Value; - - if (value == null) - { - if (stack != null) - { - stack.TryPop(out _); - } - } - else - { - if (stack == null) - { - stack = new ConcurrentStack(); - } - stack.Push(value); - s_scopeContextStack.Value = stack; - } - } - - - private T? GetHttpContextObject(string key, bool required = true) - where T : class - { - if (!_requestCache.IsAvailable && required) - { - throw new Exception("Request cache is unavailable."); - } - - var stack = (ConcurrentStack?)_requestCache.Get(key); - return stack != null && stack.TryPeek(out T? peek) ? peek : null; - } - - private bool SetHttpContextObject(string key, T? value, bool required = true) - { - if (!_requestCache.IsAvailable) - { - if (required) - { - throw new Exception("Request cache is unavailable."); - } - - return false; - } - -#if DEBUG_SCOPES - // manage the 'context' that contains the scope (null, "http" or "call") - // only for scopes of course! - if (key == s_scopeItemKey) - { - // first, null-register the existing value - var ambientScope = (IScope)_requestCache.Get(s_scopeItemKey); - if (ambientScope != null) - { - RegisterContext(ambientScope, null); - } - - // then register the new value - if (value is IScope scope) - { - RegisterContext(scope, "http"); - } - } -#endif - var stack = (ConcurrentStack?)_requestCache.Get(key); - - if (value == null) - { - if (stack != null) - { - stack.TryPop(out _); - } - } - else - { - if (stack == null) - { - stack = new ConcurrentStack(); - } - stack.Push(value); - _requestCache.Set(key, stack); - } - - return true; - } - - #endregion #region Ambient Context @@ -270,83 +103,23 @@ namespace Umbraco.Cms.Infrastructure.Scoping /// /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal) /// - public IScopeContext? AmbientContext - { - get - { - // try http context, fallback onto call context - IScopeContext? value = GetHttpContextObject(s_contextItemKey, false); - if (value != null) - { - return value; - } - - ConcurrentStack? stack = s_scopeContextStack.Value; - if (stack == null || !stack.TryPeek(out IScopeContext? peek)) - { - return null; - } - - return peek; - } - } + public IScopeContext? AmbientContext => _ambientContextStack.AmbientContext; #endregion #region Ambient Scope - IScope? IScopeAccessor.AmbientScope => AmbientScope; - /// /// Gets or set the Ambient (Current) for the current execution context. /// /// /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal) /// - public Scope? AmbientScope - { - get - { - // try http context, fallback onto call context - IScope? value = GetHttpContextObject(s_scopeItemKey, false); - if (value != null) - { - return (Scope)value; - } + public Scope? AmbientScope => (Scope?)_ambientScopeStack.AmbientScope; - ConcurrentStack? stack = s_scopeStack.Value; - if (stack == null || !stack.TryPeek(out IScope? peek)) - { - return null; - } + IScope? IScopeAccessor.AmbientScope => _ambientScopeStack.AmbientScope; - return (Scope)peek; - } - } - - public void PopAmbientScope(Scope? scope) - { - // pop the stack from all contexts - SetHttpContextObject(s_scopeItemKey, null, false); - SetCallContextScope(null); - - // We need to move the stack to a different context if the parent scope - // is flagged with a different CallContext flag. This is required - // if creating a child scope with callContext: true (thus forcing CallContext) - // when there is actually a current HttpContext available. - // It's weird but is required for Deploy somehow. - bool parentScopeCallContext = (scope?.ParentScope?.CallContext ?? false); - if ((scope?.CallContext ?? false) && !parentScopeCallContext) - { - MoveCallContextScopeToHttpContext(); - MoveCallContextScopeContextToHttpContext(); - } - else if ((!scope?.CallContext ?? false) && parentScopeCallContext) - { - MoveHttpContextScopeToCallContext(); - MoveHttpContextScopeContextToCallContext(); - } - } + public void PopAmbientScope() => _ambientScopeStack.Pop(); #endregion @@ -357,22 +130,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping throw new ArgumentNullException(nameof(scope)); } - if (scope.CallContext != false || !SetHttpContextObject(s_scopeItemKey, scope, false)) - { - // In this case, always ensure that the HttpContext items - // is transfered to CallContext and then cleared since we - // may be migrating context with the callContext = true flag. - // This is a weird case when forcing callContext when HttpContext - // is available. Required by Deploy. - - if (_requestCache.IsAvailable) - { - MoveHttpContextScopeToCallContext(); - MoveHttpContextScopeContextToCallContext(); - } - - SetCallContextScope(scope); - } + _ambientScopeStack.Push(scope); } public void PushAmbientScopeContext(IScopeContext? scopeContext) @@ -381,17 +139,10 @@ namespace Umbraco.Cms.Infrastructure.Scoping { throw new ArgumentNullException(nameof(scopeContext)); } - - SetHttpContextObject(s_contextItemKey, scopeContext, false); - SetCallContextScopeContext(scopeContext); + _ambientContextStack.Push(scopeContext); } - public void PopAmbientScopeContext() - { - // pop stack from all contexts - SetHttpContextObject(s_contextItemKey, null, false); - SetCallContextScopeContext(null); - } + public void PopAmbientScopeContext() => _ambientContextStack.Pop(); /// public IScope CreateDetachedScope( @@ -445,7 +196,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping throw new InvalidOperationException("Ambient scope is not detachable."); } - PopAmbientScope(ambientScope); + PopAmbientScope(); PopAmbientScopeContext(); Scope? originalScope = AmbientScope; @@ -499,9 +250,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping /// public IScopeContext? Context => AmbientContext; - // for testing - internal ConcurrentStack? GetCallContextScopeValue() => s_scopeStack.Value; - #if DEBUG_SCOPES // this code needs TLC // diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs index cfd9e49583..5febb2819b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs @@ -226,56 +226,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping Assert.IsNull(scopeProvider.AmbientScope); } - [Test] - public void NestedMigrateScope() - { - // Get the request cache mock and re-configure it to be available and used - var requestCacheDictionary = new Dictionary(); - IRequestCache requestCache = AppCaches.RequestCache; - var requestCacheMock = Mock.Get(requestCache); - requestCacheMock - .Setup(x => x.IsAvailable) - .Returns(true); - requestCacheMock - .Setup(x => x.Set(It.IsAny(), It.IsAny())) - .Returns((string key, object val) => - { - requestCacheDictionary.Add(key, val); - return true; - }); - requestCacheMock - .Setup(x => x.Get(It.IsAny())) - .Returns((string key) => requestCacheDictionary.TryGetValue(key, out var val) ? val : null); - - ScopeProvider scopeProvider = ScopeProvider; - Assert.IsNull(scopeProvider.AmbientScope); - - using (IScope scope = scopeProvider.CreateScope()) - { - Assert.IsInstanceOf(scope); - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(scope, scopeProvider.AmbientScope); - - using (IScope nested = scopeProvider.CreateScope(callContext: true)) - { - Assert.IsInstanceOf(nested); - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(nested, scopeProvider.AmbientScope); - Assert.AreSame(scope, ((Scope)nested).ParentScope); - - // it's moved over to call context - ConcurrentStack callContextScope = scopeProvider.GetCallContextScopeValue(); - - Assert.IsNotNull(callContextScope); - Assert.AreEqual(2, callContextScope.Count); - } - - // it's naturally back in http context - } - - Assert.IsNull(scopeProvider.AmbientScope); - } - [Test] public void NestedCreateScopeContext() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs index 88adef3824..4ee52a6869 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs @@ -134,11 +134,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { try { - ConcurrentStack - currentStack = ((ScopeProvider)ScopeProvider).GetCallContextScopeValue(); - log.LogInformation("[{ThreadId}] Current Stack? {CurrentStack}", - Thread.CurrentThread.ManagedThreadId, currentStack?.Count); - // NOTE: This is NULL because we have supressed the execution context flow. // If we don't do that we will get various exceptions because we're trying to run concurrent threads // against an ambient context which cannot be done due to the rules of scope creation and completion. @@ -234,11 +229,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { try { - ConcurrentStack - currentStack = ((ScopeProvider)ScopeProvider).GetCallContextScopeValue(); - log.LogInformation("[{ThreadId}] Current Stack? {CurrentStack}", - Thread.CurrentThread.ManagedThreadId, currentStack?.Count); - // NOTE: This is NULL because we have supressed the execution context flow. // If we don't do that we will get various exceptions because we're trying to run concurrent threads // against an ambient context which cannot be done due to the rules of scope creation and completion. diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 7593e52511..8aca86ba80 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components Mock.Of(), Options.Create(new ContentSettings())); IEventAggregator eventAggregator = Mock.Of(); - var scopeProvider = new ScopeProvider(Mock.Of(),f , fs, new TestOptionsMonitor(coreDebug), mediaFileManager, loggerFactory, NoAppCache.Instance, eventAggregator); + var scopeProvider = new ScopeProvider(new AmbientScopeStack(), new AmbientScopeContextStack(), Mock.Of(),f , fs, new TestOptionsMonitor(coreDebug), mediaFileManager, loggerFactory, eventAggregator); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs index 6ddc506753..79d2f7b3d3 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs @@ -93,13 +93,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping eventAggregatorMock = new Mock(); return new ScopeProvider( + new AmbientScopeStack(), + new AmbientScopeContextStack(), Mock.Of(), Mock.Of(), fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), mediaFileManager, loggerFactory, - Mock.Of(), eventAggregatorMock.Object ); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs index 5734622e3e..3d4ded44f2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs @@ -71,13 +71,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Scoping sqlContext.Setup(x => x.SqlSyntax).Returns(syntaxProviderMock.Object); return new ScopeProvider( + new AmbientScopeStack(), + new AmbientScopeContextStack(), lockingMechanismFactory.Object, databaseFactory.Object, fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), mediaFileManager, loggerFactory, - Mock.Of(), Mock.Of()); } From d7ce136107a7867d9c44a6a9fd03d43f80fdbfe5 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 7 Jun 2022 11:41:46 +0100 Subject: [PATCH 07/27] v10: Prefer SQLite primitive types to flexible types (#12541) * Prefer SQLite primitive types to flexible types. * SQLite - column mappings use TEXT for decimals Thanks @mattbrailsford for sense check. --- .../Services/SqliteSyntaxProvider.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs index f7d3a299ef..4f90817e47 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs @@ -38,6 +38,20 @@ public class SqliteSyntaxProvider : SqlSyntaxProviderBase [typeof(Guid)] = new SqliteGuidScalarMapper(), [typeof(Guid?)] = new SqliteNullableGuidScalarMapper(), }; + + IntColumnDefinition = "INTEGER"; + LongColumnDefinition = "INTEGER"; + BoolColumnDefinition = "INTEGER"; + + GuidColumnDefinition = "TEXT"; + DateTimeColumnDefinition = "TEXT"; + DateTimeOffsetColumnDefinition = "TEXT"; + TimeColumnDefinition = "TEXT"; + DecimalColumnDefinition = "TEXT"; // REAL would be lossy. - https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite/types + + RealColumnDefinition = "REAL"; + + BlobColumnDefinition = "BLOB"; } /// From a8b68202f3d8e7e94597f85a1f50f2fdef667bc9 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 7 Jun 2022 12:15:26 +0100 Subject: [PATCH 08/27] Fix issue where languages files are not found in subdir of package dir (#12543) --- .../UmbracoBuilder.LocalizedText.cs | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs index ff817e2f1c..2d18905ee9 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs @@ -1,11 +1,6 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; - using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; - using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; @@ -17,6 +12,7 @@ namespace Umbraco.Extensions /// public static partial class UmbracoBuilderExtensions { + /// /// Adds the supplementary localized texxt file sources from the various physical and virtual locations supported. /// @@ -69,45 +65,52 @@ namespace Umbraco.Extensions private static IEnumerable GetPluginLanguageFileSources( IFileProvider fileProvider, string folder, bool overwriteCoreKeys) { - // locate all the *.xml files inside Lang folders inside folders of the main folder - // e.g. /app_plugins/plugin-name/lang/*.xml - var fileSources = new List(); - - var pluginFolders = fileProvider.GetDirectoryContents(folder) - .Where(x => x.IsDirectory).ToList(); + IEnumerable pluginFolders = fileProvider + .GetDirectoryContents(folder) + .Where(x => x.IsDirectory); foreach (IFileInfo pluginFolder in pluginFolders) { // get the full virtual path for the plugin folder var pluginFolderPath = WebPath.Combine(folder, pluginFolder.Name); - // get any lang folders in this plugin - IEnumerable langFolders = fileProvider.GetDirectoryContents(pluginFolderPath) - .Where(x => x.IsDirectory && x.Name.InvariantEquals("lang")); - // loop through the lang folder(s) // - there could be multiple on case sensitive file system - foreach (var langFolder in langFolders) + foreach (var langFolder in GetLangFolderPaths(fileProvider, pluginFolderPath)) { - // get the full 'virtual' path of the lang folder - var langFolderPath = WebPath.Combine(pluginFolderPath, langFolder.Name); - // request all the files out of the path, these will have physicalPath set. - var files = fileProvider.GetDirectoryContents(langFolderPath) - .Where(x => x.Name.InvariantEndsWith(".xml") && !string.IsNullOrEmpty(x.PhysicalPath)) - .Select(x => new FileInfo(x.PhysicalPath)) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, overwriteCoreKeys)) - .ToList(); + IEnumerable localizationFiles = fileProvider + .GetDirectoryContents(langFolder) + .Where(x => !string.IsNullOrEmpty(x.PhysicalPath)) + .Where(x => x.Name.InvariantEndsWith(".xml")) + .Select(x => new FileInfo(x.PhysicalPath)); - // add any to our results - if (files.Count > 0) + foreach (FileInfo file in localizationFiles) { - fileSources.AddRange(files); + yield return new LocalizedTextServiceSupplementaryFileSource(file, overwriteCoreKeys); } } } + } - return fileSources; + private static IEnumerable GetLangFolderPaths(IFileProvider fileProvider, string path) + { + IEnumerable directories = fileProvider.GetDirectoryContents(path).Where(x => x.IsDirectory); + + foreach (IFileInfo directory in directories) + { + var virtualPath = WebPath.Combine(path, directory.Name); + + if (directory.Name.InvariantEquals("lang")) + { + yield return virtualPath; + } + + foreach (var nested in GetLangFolderPaths(fileProvider, virtualPath)) + { + yield return nested; + } + } } } } From fffbbbeb5fc5a2aab6a7e862fed19b75c12013c4 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 7 Jun 2022 14:45:42 +0200 Subject: [PATCH 09/27] Make FindContent return type nullable (#12545) --- src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs index 616611a224..3bf8d3a04a 100644 --- a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs +++ b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs @@ -11,5 +11,5 @@ public interface IVirtualPageController /// /// Returns the to use as the current page for the request /// - IPublishedContent FindContent(ActionExecutingContext actionExecutingContext); + IPublishedContent? FindContent(ActionExecutingContext actionExecutingContext); } From 9ad790f36b766fb59b07735454a804e268bd0642 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 8 Jun 2022 08:19:43 +0200 Subject: [PATCH 10/27] Updated nuget dependencies (07-06-2022) (#12525) * Updated nuget dependencies * Move Nerdbank.GitVersioning update to Directory.Build.props * Updated more dependencies --- Directory.Build.props | 2 +- src/JsonSchema/JsonSchema.csproj | 9 ++++++--- .../Umbraco.Cms.Persistence.Sqlite.csproj | 2 +- src/Umbraco.Cms/Umbraco.Cms.csproj | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 4 ++-- .../Logging/MessageTemplates.cs | 4 ++-- .../Umbraco.Infrastructure.csproj | 15 +++++++++------ .../Umbraco.Web.BackOffice.csproj | 3 +++ src/Umbraco.Web.Common/Umbraco.Web.Common.csproj | 10 +++++----- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- .../Umbraco.Web.Website.csproj | 3 +++ templates/Umbraco.Templates.csproj | 4 ++-- .../Umbraco.Tests.Benchmarks.csproj | 2 +- .../Umbraco.Tests.Common.csproj | 2 +- .../Umbraco.Tests.Integration.csproj | 6 +++--- .../Umbraco.Tests.UnitTests.csproj | 2 +- 16 files changed, 42 insertions(+), 30 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index da9b4e3fa7..fa8410d94d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ all - 3.5.103 + 3.5.107 diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 97608c756b..5ab8db1d99 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -7,13 +7,16 @@ - - + + - + + + 3.5.107 + diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj index 57055ec96b..5aa062df17 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj +++ b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Umbraco.Cms/Umbraco.Cms.csproj b/src/Umbraco.Cms/Umbraco.Cms.csproj index 19f64a60aa..df2474b76c 100644 --- a/src/Umbraco.Cms/Umbraco.Cms.csproj +++ b/src/Umbraco.Cms/Umbraco.Cms.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8bcead30bb..01183739d5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -16,10 +16,10 @@ - + - + diff --git a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs index 305844d7d6..3e497673ce 100644 --- a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs +++ b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs @@ -30,14 +30,14 @@ namespace Umbraco.Cms.Core.Logging if (!bound) throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args."); - var values = boundProperties.ToDictionary(x => x.Name, x => x.Value); + var values = boundProperties!.ToDictionary(x => x.Name, x => x.Value); // this ends up putting every string parameter between quotes //return parsedTemplate.Render(values); // this does not var tw = new StringWriter(); - foreach (var t in parsedTemplate.Tokens) + foreach (var t in parsedTemplate!.Tokens) { if (t is PropertyToken pt && values.TryGetValue(pt.PropertyName, out var propVal) && diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index c304f4e99a..2c6d379073 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -18,9 +18,9 @@ - + - + @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -37,10 +37,10 @@ - + - + @@ -48,7 +48,7 @@ - + @@ -57,6 +57,9 @@ all + + 3.5.107 + diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 13076bd50c..3f24e2717e 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -28,6 +28,9 @@ all + + 3.5.107 + diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index a2c7dcb475..e08f45e5af 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -35,14 +35,14 @@ - - + + - - - + + + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2274c73327..42e894a7f3 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj index 0bb0b6c0ad..176564eaf8 100644 --- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj +++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj @@ -35,6 +35,9 @@ all + + 3.5.107 + diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index 182b220167..47b2246835 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -1,7 +1,7 @@  - + net6.0 Template @@ -14,7 +14,7 @@ true false - + diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index db34088068..29e0a36353 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -29,7 +29,7 @@ 6.0.0 - 4.17.2 + 4.18.1 diff --git a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index 705f3333f9..05a5554db2 100644 --- a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 1954093f66..a6b04cac2b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -86,12 +86,12 @@ - - + + - + all diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 70bf694128..55d875d1bc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -21,7 +21,7 @@ - + From ce8f5f4724b29e70f84d21f6ad1362b21a6b0ecb Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Wed, 8 Jun 2022 08:25:25 +0100 Subject: [PATCH 11/27] Improve FlagOutOfDateModels property behaviour. (cherry picked from commit 54077725c373495fce0d3fbc5cdb6469aad3b676) --- .../Configuration/Models/ModelsBuilderSettings.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index 73d046de32..9e14391557 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -42,18 +42,17 @@ namespace Umbraco.Cms.Core.Configuration.Models /// public bool FlagOutOfDateModels { - get => _flagOutOfDateModels; - - set + get { if (!ModelsMode.IsAuto()) { - _flagOutOfDateModels = false; - return; + return false; } - _flagOutOfDateModels = value; + return _flagOutOfDateModels; } + + set => _flagOutOfDateModels = value; } /// From cf25d29f904f179098e3f8af189afbe4788c5d60 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Wed, 8 Jun 2022 08:16:26 +0100 Subject: [PATCH 12/27] Fix logic error WRT models builder flag out of date models. (#12548) (cherry picked from commit 6b0149803a879d1c6902a5f61d1f2e9dc8545aac) --- src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index 9e14391557..0d8908fd1c 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -44,7 +44,7 @@ namespace Umbraco.Cms.Core.Configuration.Models { get { - if (!ModelsMode.IsAuto()) + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsAuto()) { return false; } From 7e8c3cb20df56a5e2b39f47a977c7113201d06ee Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 8 Jun 2022 11:05:53 +0200 Subject: [PATCH 13/27] Fixed issue with expected null value. (#12550) Fixes https://github.com/umbraco/Umbraco-CMS/issues/12526 --- src/Umbraco.Web.Common/Views/UmbracoViewPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index b1ac11c77d..0b3132b795 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -128,7 +128,7 @@ public abstract class UmbracoViewPage : RazorPage { // filter / add preview banner // ASP.NET default value is text/html - if (Context.Response.ContentType.InvariantContains("text/html")) + if (Context.Response?.ContentType?.InvariantContains("text/html") ?? false) { if (((UmbracoContext?.IsDebug ?? false) || (UmbracoContext?.InPreviewMode ?? false)) && tagHelperOutput.TagName != null From 2cc229ef9afdc966a1f327f7e447fb3aff4c51ae Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 8 Jun 2022 21:24:19 +0200 Subject: [PATCH 14/27] Updated Examine to 3.0.0 --- src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj | 2 +- src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj | 2 +- .../Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj index db5a303efd..833e01be50 100644 --- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj +++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj @@ -21,7 +21,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index 2c6d379073..fc41bec6c5 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -53,7 +53,7 @@ - + all diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index a6b04cac2b..8397da51b4 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -85,7 +85,7 @@ - + From 2ab1518862532124d5442ddd425ee37b42db2d2e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jun 2022 09:46:42 +0200 Subject: [PATCH 15/27] Fixes relation issue, when moving a root item to recycle bin, the "Relate Parent Media Folder On Delete"/"Relate Parent Document On Delete" cannot get the parent node type, because it is a fake root. --- src/Umbraco.Core/Models/RelationItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs index 75344914f0..634ee53bb2 100644 --- a/src/Umbraco.Core/Models/RelationItem.cs +++ b/src/Umbraco.Core/Models/RelationItem.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Core.Models public string? NodeType { get; set; } [DataMember(Name = "udi")] - public Udi NodeUdi => Udi.Create(NodeType, NodeKey); + public Udi NodeUdi => NodeType == Constants.UdiEntityType.Unknown ? null : Udi.Create(NodeType, NodeKey); [DataMember(Name = "icon")] public string? ContentTypeIcon { get; set; } From d7b015d8c9fd3dfd7b92c0fd62f12e5d0bab47fe Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jun 2022 09:53:28 +0200 Subject: [PATCH 16/27] Fix possible null error --- src/Umbraco.Core/Models/RelationItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs index 634ee53bb2..b37afe4d70 100644 --- a/src/Umbraco.Core/Models/RelationItem.cs +++ b/src/Umbraco.Core/Models/RelationItem.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Core.Models public string? NodeType { get; set; } [DataMember(Name = "udi")] - public Udi NodeUdi => NodeType == Constants.UdiEntityType.Unknown ? null : Udi.Create(NodeType, NodeKey); + public Udi? NodeUdi => NodeType == Constants.UdiEntityType.Unknown ? null : Udi.Create(NodeType, NodeKey); [DataMember(Name = "icon")] public string? ContentTypeIcon { get; set; } From 116d5735fb1b9055535f9bc2da8a87a9193ea224 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jun 2022 10:12:47 +0200 Subject: [PATCH 17/27] Bump version to 10.0.0 final --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3e15f3bd2a..0f31ff96d5 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.0.0-rc5", + "version": "10.0.0", "assemblyVersion": { "precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor. }, From e3f4b86f1c28e0ec30a1ad2a079e308af4cbfbf7 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 10 Jun 2022 09:03:59 +0100 Subject: [PATCH 18/27] Fix attempting to write lock files to LocalTempPath before it exists (#12563) --- .../Runtime/FileSystemMainDomLock.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs index 37e962a222..a7b1610010 100644 --- a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs @@ -1,8 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -14,6 +10,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime internal class FileSystemMainDomLock : IMainDomLock { private readonly ILogger _logger; + private readonly IHostingEnvironment _hostingEnvironment; private readonly IOptionsMonitor _globalSettings; private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly string _lockFilePath; @@ -29,6 +26,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime IOptionsMonitor globalSettings) { _logger = logger; + _hostingEnvironment = hostingEnvironment; _globalSettings = globalSettings; var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock"; @@ -45,6 +43,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime { try { + Directory.CreateDirectory(_hostingEnvironment.LocalTempPath); _logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath); _lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); DeleteLockReleaseSignalFile(); From 5049e7071b381afe85df66ccd231eeb85451eecb Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 21 Jun 2022 08:50:00 +0200 Subject: [PATCH 19/27] Fix redirect after logout bug https://github.com/umbraco/Umbraco-CMS/issues/12592 --- .../src/views/common/login.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js index b33d707c94..5827f7e530 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js @@ -6,9 +6,9 @@ angular.module('umbraco').controller("Umbraco.LoginController", function (events var evtOn = eventsService.on("app.ready", function(evt, data){ $scope.avatar = "assets/img/application/logo.png"; - var path = "/"; + var path = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath; - //check if there's a returnPath query string, if so redirect to it + //check if there's a returnPath query string, if so redirect to it var locationObj = $location.search(); if (locationObj.returnPath) { path = decodeURIComponent(locationObj.returnPath); From 389380d8fa89c4ea515de20233479a2f525b9417 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Jun 2022 11:44:28 +0200 Subject: [PATCH 20/27] V10: Fix sending content notification (#12597) * Add mappers to map between ContentItemDisplay and ContentItemDisplayWithSchedule * Ensure SendingContentNotification is always sent * Add custom setup hook for UmbracoTestServerTestBase * Add test showing bug/fix * Test schedule being mapped correctly * Obsolete the old constructor * Removed TODO --- .../OutgoingEditorModelEventAttribute.cs | 48 +++++- .../Mapping/ContentMapDefinition.cs | 123 +++++++++++++ .../UmbracoTestServerTestBase.cs | 15 +- .../OutgoingEditorModelEventFilterTests.cs | 163 ++++++++++++++++++ 4 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs diff --git a/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs index 64ac33b1aa..171e8a1bf9 100644 --- a/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs @@ -3,12 +3,15 @@ using System.Collections; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Filters @@ -29,20 +32,34 @@ namespace Umbraco.Cms.Web.BackOffice.Filters private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - + private readonly IUmbracoMapper _mapper; private readonly IEventAggregator _eventAggregator; - public OutgoingEditorModelEventFilter( + [ActivatorUtilitiesConstructor]public OutgoingEditorModelEventFilter( IUmbracoContextAccessor umbracoContextAccessor, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IEventAggregator eventAggregator) - { + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IEventAggregator eventAggregator, + IUmbracoMapper mapper){ _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _backOfficeSecurityAccessor = backOfficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backOfficeSecurityAccessor)); _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); - } + _mapper = mapper; + } + + [Obsolete("Please use constructor that takes an IUmbracoMapper, scheduled for removal in V12")] + public OutgoingEditorModelEventFilter( + IUmbracoContextAccessor umbracoContextAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IEventAggregator eventAggregator) + : this( + umbracoContextAccessor, + backOfficeSecurityAccessor, + eventAggregator, + StaticServiceProvider.Instance.GetRequiredService()) + { + } public void OnActionExecuted(ActionExecutedContext context) { @@ -72,7 +89,26 @@ namespace Umbraco.Cms.Web.BackOffice.Filters case ContentItemDisplay content: _eventAggregator.Publish(new SendingContentNotification(content, umbracoContext)); break; - case MediaItemDisplay media: + case ContentItemDisplayWithSchedule contentWithSchedule: + // This is a bit weird, since ContentItemDisplayWithSchedule was introduced later, + // the SendingContentNotification only accepts ContentItemDisplay, + // which means we have to map it to this before sending the notification. + ContentItemDisplay? display = _mapper.Map(contentWithSchedule); + if (display is null) + { + // This will never happen. + break; + } + + // Now that the display is mapped to the non-schedule one we can publish the notification. + _eventAggregator.Publish(new SendingContentNotification(display, umbracoContext)); + + // We want the changes the handler makes to take effect. + // So we have to map these changes back to the existing ContentItemWithSchedule. + // To avoid losing the schedule information we add the old variants to context. + _mapper.Map(display, contentWithSchedule, mapperContext => mapperContext.Items[nameof(contentWithSchedule.Variants)] = contentWithSchedule.Variants); + break; + case MediaItemDisplay media: _eventAggregator.Publish(new SendingMediaNotification(media, umbracoContext)); break; case MemberDisplay member: diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index 24cd1c5cbe..9921b59224 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -107,7 +107,130 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping mapper.Define((source, context) => new ContentVariantDisplay(), Map); mapper.Define((source, context) => new ContentVariantScheduleDisplay(), Map); + mapper.Define((source, context) => new ContentItemDisplay(), Map); + mapper.Define((source, context) => new ContentItemDisplayWithSchedule(), Map); + + mapper.Define((source, context) => new ContentVariantScheduleDisplay(), Map); + mapper.Define((source, context) => new ContentVariantDisplay(), Map); + } + + // Umbraco.Code.MapAll + private void Map(ContentVariantScheduleDisplay source, ContentVariantDisplay target, MapperContext context) + { + target.CreateDate = source.CreateDate; + target.DisplayName = source.DisplayName; + target.Language = source.Language; + target.Name = source.Name; + target.PublishDate = source.PublishDate; + target.Segment = source.Segment; + target.State = source.State; + target.Tabs = source.Tabs; + target.UpdateDate = source.UpdateDate; + } + + // Umbraco.Code.MapAll + private void Map(ContentItemDisplay source, ContentItemDisplayWithSchedule target, MapperContext context) + { + target.AllowedActions = source.AllowedActions; + target.AllowedTemplates = source.AllowedTemplates; + target.AllowPreview = source.AllowPreview; + target.ContentApps = source.ContentApps; + target.ContentDto = source.ContentDto; + target.ContentTypeAlias = source.ContentTypeAlias; + target.ContentTypeId = source.ContentTypeId; + target.ContentTypeKey = source.ContentTypeKey; + target.ContentTypeName = source.ContentTypeName; + target.DocumentType = source.DocumentType; + target.Errors = source.Errors; + target.Icon = source.Icon; + target.Id = source.Id; + target.IsBlueprint = source.IsBlueprint; + target.IsChildOfListView = source.IsChildOfListView; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Owner = source.Owner; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.PersistedContent = source.PersistedContent; + target.SortOrder = source.SortOrder; + target.TemplateAlias = source.TemplateAlias; + target.TemplateId = source.TemplateId; + target.Trashed = source.Trashed; + target.TreeNodeUrl = source.TreeNodeUrl; + target.Udi = source.Udi; + target.UpdateDate = source.UpdateDate; + target.Updater = source.Updater; + target.Urls = source.Urls; + target.Variants = context.MapEnumerable(source.Variants); + } + + // Umbraco.Code.MapAll + private void Map(ContentVariantDisplay source, ContentVariantScheduleDisplay target, MapperContext context) + { + target.CreateDate = source.CreateDate; + target.DisplayName = source.DisplayName; + target.Language = source.Language; + target.Name = source.Name; + target.PublishDate = source.PublishDate; + target.Segment = source.Segment; + target.State = source.State; + target.Tabs = source.Tabs; + target.UpdateDate = source.UpdateDate; + + // We'll only try and map the ReleaseDate/ExpireDate if the "old" ContentVariantScheduleDisplay is in the context, otherwise we'll just skip it quietly. + _ = context.Items.TryGetValue(nameof(ContentItemDisplayWithSchedule.Variants), out var variants); + if (variants is IEnumerable scheduleDisplays) + { + ContentVariantScheduleDisplay? item = scheduleDisplays.FirstOrDefault(x => x.Language?.Id == source.Language?.Id && x.Segment == source.Segment); + + if (item is null) + { + // If we can't find the old variants display, we'll just not try and map it. + return; + } + + target.ReleaseDate = item.ReleaseDate; + target.ExpireDate = item.ExpireDate; } + } + + // Umbraco.Code.MapAll + private static void Map(ContentItemDisplayWithSchedule source, ContentItemDisplay target, MapperContext context) + { + target.AllowedActions = source.AllowedActions; + target.AllowedTemplates = source.AllowedTemplates; + target.AllowPreview = source.AllowPreview; + target.ContentApps = source.ContentApps; + target.ContentDto = source.ContentDto; + target.ContentTypeAlias = source.ContentTypeAlias; + target.ContentTypeId = source.ContentTypeId; + target.ContentTypeKey = source.ContentTypeKey; + target.ContentTypeName = source.ContentTypeName; + target.DocumentType = source.DocumentType; + target.Errors = source.Errors; + target.Icon = source.Icon; + target.Id = source.Id; + target.IsBlueprint = source.IsBlueprint; + target.IsChildOfListView = source.IsChildOfListView; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Owner = source.Owner; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.PersistedContent = source.PersistedContent; + target.SortOrder = source.SortOrder; + target.TemplateAlias = source.TemplateAlias; + target.TemplateId = source.TemplateId; + target.Trashed = source.Trashed; + target.TreeNodeUrl = source.TreeNodeUrl; + target.Udi = source.Udi; + target.UpdateDate = source.UpdateDate; + target.Updater = source.Updater; + target.Urls = source.Urls; + target.Variants = source.Variants; + } // Umbraco.Code.MapAll private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index f1f8b124e8..1ec90424f3 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -42,6 +42,16 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected WebApplicationFactory Factory { get; private set; } + /// + /// Hook for altering UmbracoBuilder setup + /// + /// + /// Can also be used for registering test doubles. + /// + protected virtual void CustomTestSetup(IUmbracoBuilder builder) + { + } + [SetUp] public void Setup() { @@ -233,8 +243,9 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .AddWebsite() .AddUmbracoSqlServerSupport() .AddUmbracoSqliteSupport() - .AddTestServices(TestHelper) // This is the important one! - .Build(); + .AddTestServices(TestHelper); // This is the important one! + CustomTestSetup(builder); + builder.Build(); } /// diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs new file mode 100644 index 0000000000..e627a3300f --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Integration.TestServerTest; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Formatters; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Filters; + +[TestFixture] +public class OutgoingEditorModelEventFilterTests : UmbracoTestServerTestBase +{ + private static int _messageCount; + private static Action _handler; + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } + + [TearDown] + public void Reset() => ResetNotifications(); + + [Test] + public async Task Content_Item_With_Schedule_Raises_SendingContentNotification() + { + IContentTypeService contentTypeService = GetRequiredService(); + IContentService contentService = GetRequiredService(); + IJsonSerializer serializer = GetRequiredService(); + + var contentType = new ContentTypeBuilder().Build(); + contentTypeService.Save(contentType); + + var contentToRequest = new ContentBuilder() + .WithoutIdentity() + .WithContentType(contentType) + .Build(); + + contentService.Save(contentToRequest); + + _handler = notification => notification.Content.AllowPreview = false; + + var url = PrepareApiControllerUrl(x => x.GetById(contentToRequest.Id)); + + HttpResponseMessage response = await Client.GetAsync(url); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + + var text = await response.Content.ReadAsStringAsync(); + text = text.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + var display = serializer.Deserialize(text); + + Assert.AreEqual(1, _messageCount); + Assert.IsNotNull(display); + Assert.IsFalse(display.AllowPreview); + } + + [Test] + public async Task Publish_Schedule_Is_Mapped_Correctly() + { + const string UsIso = "en-US"; + const string DkIso = "da-DK"; + const string SweIso = "sv-SE"; + var contentTypeService = GetRequiredService(); + var contentService = GetRequiredService(); + var localizationService = GetRequiredService(); + IJsonSerializer serializer = GetRequiredService(); + + var contentType = new ContentTypeBuilder() + .WithContentVariation(ContentVariation.Culture) + .Build(); + contentTypeService.Save(contentType); + + var dkLang = new LanguageBuilder() + .WithCultureInfo(DkIso) + .WithIsDefault(false) + .Build(); + + var sweLang = new LanguageBuilder() + .WithCultureInfo(SweIso) + .WithIsDefault(false) + .Build(); + + localizationService.Save(dkLang); + localizationService.Save(sweLang); + + var content = new ContentBuilder() + .WithoutIdentity() + .WithContentType(contentType) + .WithCultureName(UsIso, "Same Name") + .WithCultureName(SweIso, "Same Name") + .WithCultureName(DkIso, "Same Name") + .Build(); + + contentService.Save(content); + var schedule = new ContentScheduleCollection(); + + var dkReleaseDate = new DateTime(2022, 06, 22, 21, 30, 42); + var dkExpireDate = new DateTime(2022, 07, 15, 18, 00, 00); + + var sweReleaseDate = new DateTime(2022, 06, 23, 22, 30, 42); + var sweExpireDate = new DateTime(2022, 07, 10, 14, 20, 00); + schedule.Add(DkIso, dkReleaseDate, dkExpireDate); + schedule.Add(SweIso, sweReleaseDate, sweExpireDate); + contentService.PersistContentSchedule(content, schedule); + + var url = PrepareApiControllerUrl(x => x.GetById(content.Id)); + + HttpResponseMessage response = await Client.GetAsync(url); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + + var text = await response.Content.ReadAsStringAsync(); + text = text.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + var display = serializer.Deserialize(text); + + Assert.IsNotNull(display); + Assert.AreEqual(1, _messageCount); + + var dkVariant = display.Variants.FirstOrDefault(x => x.Language?.IsoCode == DkIso); + Assert.IsNotNull(dkVariant); + Assert.AreEqual(dkReleaseDate, dkVariant.ReleaseDate); + Assert.AreEqual(dkExpireDate, dkVariant.ExpireDate); + + var sweVariant = display.Variants.FirstOrDefault(x => x.Language?.IsoCode == SweIso); + Assert.IsNotNull(sweVariant); + Assert.AreEqual(sweReleaseDate, sweVariant.ReleaseDate); + Assert.AreEqual(sweExpireDate, sweVariant.ExpireDate); + + var usVariant = display.Variants.FirstOrDefault(x => x.Language?.IsoCode == UsIso); + Assert.IsNotNull(usVariant); + Assert.IsNull(usVariant.ReleaseDate); + Assert.IsNull(usVariant.ExpireDate); + } + + private void ResetNotifications() + { + _messageCount = 0; + _handler = null; + } + + private class FilterEventHandler : INotificationHandler + { + public void Handle(SendingContentNotification notification) + { + _messageCount += 1; + _handler?.Invoke(notification); + } + } +} From 24222fa4d2d96d0b118fb043998fb2ed81c81ac0 Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Thu, 16 Jun 2022 15:15:04 +0200 Subject: [PATCH 21/27] Unescape returnPath. (cherry picked from commit 88af609c813edc6ad91b0891d3ee8943f91b0376) --- .../directives/components/application/umblogin.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index 425ebf3a10..26019b53f5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -46,7 +46,7 @@ vm.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; vm.errorMsg = ""; const tempUrl = new URL(Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl, window.location.origin); - tempUrl.searchParams.append("redirectUrl", $location.search().returnPath ?? "") + tempUrl.searchParams.append("redirectUrl", decodeURIComponent($location.search().returnPath ?? "")) vm.externalLoginFormAction = tempUrl.pathname + tempUrl.search; vm.externalLoginProviders = externalLoginInfoService.getLoginProviders(); From 46d71838f2730573f1cd106a21707b3f2aaeebe5 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 22 Jun 2022 08:25:18 +0200 Subject: [PATCH 22/27] Only send telemetry if we're on runtimelevel run (#12600) --- .../HostedServices/ReportSiteTask.cs | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index 54137fad99..c717569d53 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -6,31 +6,43 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Core.Telemetry.Models; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Infrastructure.HostedServices +namespace Umbraco.Cms.Infrastructure.HostedServices; + +public class ReportSiteTask : RecurringHostedServiceBase { - public class ReportSiteTask : RecurringHostedServiceBase - { - private readonly ILogger _logger; - private readonly ITelemetryService _telemetryService; - private static HttpClient s_httpClient = new(); + private static HttpClient _httpClient = new(); + private readonly ILogger _logger; + private readonly ITelemetryService _telemetryService; + private readonly IRuntimeState _runtimeState; public ReportSiteTask( ILogger logger, - ITelemetryService telemetryService) - : base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) + ITelemetryService telemetryService, + IRuntimeState runtimeState) + : base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(5)) { _logger = logger; - _telemetryService = telemetryService; - s_httpClient = new HttpClient(); + _telemetryService = telemetryService;_runtimeState = runtimeState; + _httpClient = new HttpClient(); } - [Obsolete("Use the constructor that takes ITelemetryService instead, scheduled for removal in V11")] + [Obsolete("Use the constructor that takes IRuntimeState, scheduled for removal in V12")] + public ReportSiteTask( + ILogger logger, + ITelemetryService telemetryService) + : this(logger, telemetryService, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [Obsolete("Use the constructor that takes ITelemetryService instead, scheduled for removal in V11")] public ReportSiteTask( ILogger logger, IUmbracoVersion umbracoVersion, @@ -45,7 +57,13 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// public override async Task PerformExecuteAsync(object? state) { - if (_telemetryService.TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData) is false) + 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) { _logger.LogWarning("No telemetry marker found"); @@ -54,19 +72,19 @@ namespace Umbraco.Cms.Infrastructure.HostedServices try { - if (s_httpClient.BaseAddress is null) + if (_httpClient.BaseAddress is null) { // Send data to LIVE telemetry - s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); #if DEBUG // Send data to DEBUG telemetry service - s_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); + _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); #endif } - s_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/")) { @@ -75,7 +93,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Make a HTTP Post to telemetry service // https://telemetry.umbraco.com/installs/ // Fire & Forget, do not need to know if its a 200, 500 etc - using (HttpResponseMessage response = await s_httpClient.SendAsync(request)) + using (HttpResponseMessage response = await _httpClient.SendAsync(request)) { } } From 20f9cfe2ac878c82ef7167a57bfdbc7d9594768b Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Thu, 23 Jun 2022 15:25:25 +0200 Subject: [PATCH 23/27] Fix ReportSiteTask post merge --- .../HostedServices/ReportSiteTask.cs | 116 +++++++++--------- 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index c717569d53..dc73d0cbbb 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -1,7 +1,4 @@ -using System; -using System.Net.Http; using System.Text; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -23,18 +20,19 @@ public class ReportSiteTask : RecurringHostedServiceBase private readonly ITelemetryService _telemetryService; private readonly IRuntimeState _runtimeState; - public ReportSiteTask( - ILogger logger, - ITelemetryService telemetryService, - IRuntimeState runtimeState) + public ReportSiteTask( + ILogger logger, + ITelemetryService telemetryService, + IRuntimeState runtimeState) : base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(5)) - { - _logger = logger; - _telemetryService = telemetryService;_runtimeState = runtimeState; - _httpClient = new HttpClient(); - } + { + _logger = logger; + _telemetryService = telemetryService; + _runtimeState = runtimeState; + _httpClient = new HttpClient(); + } - [Obsolete("Use the constructor that takes IRuntimeState, scheduled for removal in V12")] + [Obsolete("Use the constructor that takes IRuntimeState, scheduled for removal in V12")] public ReportSiteTask( ILogger logger, ITelemetryService telemetryService) @@ -43,68 +41,68 @@ public class ReportSiteTask : RecurringHostedServiceBase } [Obsolete("Use the constructor that takes ITelemetryService instead, scheduled for removal in V11")] - public ReportSiteTask( - ILogger logger, - IUmbracoVersion umbracoVersion, - IOptions globalSettings) - : this(logger, StaticServiceProvider.Instance.GetRequiredService()) - { - } + public ReportSiteTask( + ILogger logger, + IUmbracoVersion umbracoVersion, + IOptions globalSettings) + : this(logger, StaticServiceProvider.Instance.GetRequiredService()) + { + } - /// - /// 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) + /// + /// 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) - { - _logger.LogWarning("No telemetry marker found"); + { + _logger.LogWarning("No telemetry marker found"); - return; - } + return; + } - try + try + { + if (_httpClient.BaseAddress is null) { - if (_httpClient.BaseAddress is null) - { - // Send data to LIVE telemetry - _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + // Send data to LIVE telemetry + _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); #if DEBUG - // Send data to DEBUG telemetry service - _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); + // Send data to DEBUG telemetry service + _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); #endif - } - - - _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); - - using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/")) - { - request.Content = new StringContent(JsonConvert.SerializeObject(telemetryReportData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header - - // Make a HTTP Post to telemetry service - // https://telemetry.umbraco.com/installs/ - // Fire & Forget, do not need to know if its a 200, 500 etc - using (HttpResponseMessage response = await _httpClient.SendAsync(request)) - { - } - } } - catch + + + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + + using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/")) { - // Silently swallow - // The user does not need the logs being polluted if our service has fallen over or is down etc - // Hence only logging this at a more verbose level (which users should not be using in production) - _logger.LogDebug("There was a problem sending a request to the Umbraco telemetry service"); + request.Content = new StringContent(JsonConvert.SerializeObject(telemetryReportData), Encoding.UTF8, + "application/json"); //CONTENT-TYPE header + + // Make a HTTP Post to telemetry service + // https://telemetry.umbraco.com/installs/ + // Fire & Forget, do not need to know if its a 200, 500 etc + using (HttpResponseMessage response = await _httpClient.SendAsync(request)) + { + } } } + catch + { + // Silently swallow + // The user does not need the logs being polluted if our service has fallen over or is down etc + // Hence only logging this at a more verbose level (which users should not be using in production) + _logger.LogDebug("There was a problem sending a request to the Umbraco telemetry service"); + } } } From b232ff98a0b11cb163f2f31144cc3f356774bddf Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 27 Jun 2022 08:25:54 +0200 Subject: [PATCH 24/27] Fix formatting after merge and fix nullability --- .../Routing/PublicAccessRequestHandler.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs b/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs index 230dcaefe5..a33ac7bca2 100644 --- a/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs +++ b/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs @@ -84,15 +84,25 @@ public class PublicAccessRequestHandler : IPublicAccessRequestHandler switch (publicAccessStatus) { case PublicAccessStatus.NotLoggedIn: - _logger.LogDebug("EnsurePublishedContentAccess: Not logged in, redirect to login page"); - routeValues = await SetPublishedContentAsOtherPageAsync( - httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result!.LoginNodeId); + // redirect if this is not the login page + if (publicAccessAttempt.Result!.LoginNodeId != publishedContent.Id) + { + _logger.LogDebug("EnsurePublishedContentAccess: Not logged in, redirect to login page"); + routeValues = await SetPublishedContentAsOtherPageAsync( + httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result!.LoginNodeId); + } + break; case PublicAccessStatus.AccessDenied: - _logger.LogDebug( - "EnsurePublishedContentAccess: Current member has not access, redirect to error page"); - routeValues = await SetPublishedContentAsOtherPageAsync( - httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result!.NoAccessNodeId); + // Redirect if this is not the access denied page + if (publicAccessAttempt.Result!.NoAccessNodeId != publishedContent.Id) + { + _logger.LogDebug( + "EnsurePublishedContentAccess: Current member has not access, redirect to error page"); + routeValues = await SetPublishedContentAsOtherPageAsync( + httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result!.NoAccessNodeId); + } + break; case PublicAccessStatus.LockedOut: _logger.LogDebug("Current member is locked out, redirect to error page"); From fb35c47c702631a3967a64ef37879a97bb779084 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Mon, 27 Jun 2022 12:29:27 +0100 Subject: [PATCH 25/27] Also read the plugin files using the content provider. (#12552) --- .../DependencyInjection/UmbracoBuilder.LocalizedText.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs index 2d18905ee9..54e25240e0 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs @@ -37,6 +37,8 @@ namespace Umbraco.Extensions IFileProvider webFileProvider = webHostEnvironment.WebRootFileProvider; IFileProvider contentFileProvider = webHostEnvironment.ContentRootFileProvider; + IEnumerable localPluginFileSources = GetPluginLanguageFileSources(contentFileProvider, Cms.Core.Constants.SystemDirectories.AppPlugins, false); + // gets all langs files in /app_plugins real or virtual locations IEnumerable pluginLangFileSources = GetPluginLanguageFileSources(webFileProvider, Cms.Core.Constants.SystemDirectories.AppPlugins, false); @@ -50,7 +52,9 @@ namespace Umbraco.Extensions .SelectMany(x => x.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly)) .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); - return pluginLangFileSources + return + localPluginFileSources + .Concat(pluginLangFileSources) .Concat(userLangFileSources); } From 62eeb420044371358cc7c48594f6b37125745c9f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 29 Jun 2022 16:49:47 +0200 Subject: [PATCH 26/27] Changed const to return "Microsoft.Data.Sqlite" instead of "Microsoft.Data.SQLite" and made our checks case insensitive where possible. Sadly DbProviderFactories is still case sensative so for that we support both strings. (#12639) https://github.com/umbraco/Umbraco-CMS/issues/12604 --- .../Services/SqlServerDistributedLockingMechanism.cs | 2 +- src/Umbraco.Cms.Persistence.Sqlite/Constants.cs | 5 ++++- .../Services/SqliteDistributedLockingMechanism.cs | 2 +- .../UmbracoBuilderExtensions.cs | 5 ++++- .../Persistence/DatabaseProviderMetadataExtensions.cs | 2 +- .../Persistence/DbProviderFactoryCreator.cs | 10 +++++----- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs index 7c8effb2b3..1777ff68ce 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs @@ -39,7 +39,7 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism /// public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() && - _connectionStrings.CurrentValue.ProviderName == Constants.ProviderName; + string.Equals(_connectionStrings.CurrentValue.ProviderName,Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase); /// public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null) diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs b/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs index 76e408423c..ae58a70aa3 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs @@ -8,5 +8,8 @@ public static class Constants /// /// SQLite provider name. /// - public const string ProviderName = "Microsoft.Data.SQLite"; + public const string ProviderName = "Microsoft.Data.Sqlite"; + + [Obsolete("This will be removed in Umbraco 12. Use Constants.ProviderName instead")] + public const string ProviderNameLegacy = "Microsoft.Data.SQLite"; } diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs index 4a47d41846..1e65afae36 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs @@ -36,7 +36,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism /// public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() && - _connectionStrings.CurrentValue.ProviderName == Constants.ProviderName; + string.Equals(_connectionStrings.CurrentValue.ProviderName, Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase); // With journal_mode=wal we can always read a snapshot. public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null) diff --git a/src/Umbraco.Cms.Persistence.Sqlite/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Persistence.Sqlite/UmbracoBuilderExtensions.cs index 8843844818..f9386850fa 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/UmbracoBuilderExtensions.cs @@ -39,11 +39,14 @@ public static class UmbracoBuilderExtensions DbProviderFactories.UnregisterFactory(Constants.ProviderName); DbProviderFactories.RegisterFactory(Constants.ProviderName, Microsoft.Data.Sqlite.SqliteFactory.Instance); + DbProviderFactories.UnregisterFactory(Constants.ProviderNameLegacy); + DbProviderFactories.RegisterFactory(Constants.ProviderNameLegacy, Microsoft.Data.Sqlite.SqliteFactory.Instance); + // Prevent accidental creation of SQLite database files builder.Services.PostConfigureAll(options => { // Skip empty connection string and other providers - if (!options.IsConnectionStringConfigured() || options.ProviderName != Constants.ProviderName) + if (!options.IsConnectionStringConfigured() || (options.ProviderName != Constants.ProviderName && options.ProviderName != Constants.ProviderNameLegacy)) { return; } diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs index d0ad59fbb8..1ea941932e 100644 --- a/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs @@ -27,7 +27,7 @@ public static class DatabaseProviderMetadataExtensions /// true if a database can be created for the specified provider name; otherwise, false. /// public static bool CanForceCreateDatabase(this IEnumerable databaseProviderMetadata, string? providerName) - => databaseProviderMetadata.FirstOrDefault(x => x.ProviderName == providerName)?.ForceCreateDatabase == true; + => databaseProviderMetadata.FirstOrDefault(x => string.Equals(x.ProviderName, providerName, StringComparison.InvariantCultureIgnoreCase))?.ForceCreateDatabase == true; /// /// Generates the connection string. diff --git a/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs index 0177475609..0efa541040 100644 --- a/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs +++ b/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs @@ -44,10 +44,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence { _getFactory = getFactory; _providerSpecificInterceptors = providerSpecificInterceptors; - _databaseCreators = databaseCreators.ToDictionary(x => x.ProviderName); - _syntaxProviders = syntaxProviders.ToDictionary(x => x.ProviderName); - _bulkSqlInsertProviders = bulkSqlInsertProviders.ToDictionary(x => x.ProviderName); - _providerSpecificMapperFactories = providerSpecificMapperFactories.ToDictionary(x => x.ProviderName); + _databaseCreators = databaseCreators.ToDictionary(x => x.ProviderName, StringComparer.InvariantCultureIgnoreCase); + _syntaxProviders = syntaxProviders.ToDictionary(x => x.ProviderName, StringComparer.InvariantCultureIgnoreCase); + _bulkSqlInsertProviders = bulkSqlInsertProviders.ToDictionary(x => x.ProviderName, StringComparer.InvariantCultureIgnoreCase); + _providerSpecificMapperFactories = providerSpecificMapperFactories.ToDictionary(x => x.ProviderName, StringComparer.InvariantCultureIgnoreCase); } public DbProviderFactory? CreateFactory(string? providerName) @@ -98,6 +98,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence } public IEnumerable GetProviderSpecificInterceptors(string providerName) - => _providerSpecificInterceptors.Where(x => x.ProviderName == providerName); + => _providerSpecificInterceptors.Where(x => x.ProviderName.Equals(providerName, StringComparison.InvariantCultureIgnoreCase)); } } From fd0c4fda54b1d5cd22e8ab196801280b09672a26 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Thu, 30 Jun 2022 08:34:58 +0200 Subject: [PATCH 27/27] bumb version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 0f31ff96d5..93c727fb19 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.0.0", + "version": "10.0.1", "assemblyVersion": { "precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor. },