From e775497c5ef07b5c2da8f364d2a699c9c54a65d7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 16:09:52 +1000 Subject: [PATCH 01/92] Removes ReaderWriterLockSlim from PropertyGroupCollection and PropertyTypeCollection. No lock should exist here at all not even sure why it's there (Based on 9yr old code). I'm pretty sure it's there because these entities used to be cached (and not cloned) which meant it was the same instance used between threads. --- .../Models/PropertyGroupCollection.cs | 57 +++++++------------ .../Models/PropertyTypeCollection.cs | 51 +++++++---------- 2 files changed, 41 insertions(+), 67 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 973147b3fb..e2af5835f2 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; -using System.Threading; namespace Umbraco.Core.Models { @@ -17,8 +16,6 @@ namespace Umbraco.Core.Models // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { - private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - internal PropertyGroupCollection() { } @@ -70,47 +67,37 @@ namespace Umbraco.Core.Models internal new void Add(PropertyGroup item) { - try + //Note this is done to ensure existing groups can be renamed + if (item.HasIdentity && item.Id > 0) { - _addLocker.EnterWriteLock(); - - //Note this is done to ensure existing groups can be renamed - if (item.HasIdentity && item.Id > 0) + var exists = Contains(item.Id); + if (exists) { - var exists = Contains(item.Id); + var keyExists = Contains(item.Name); + if (keyExists) + throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates"); + + //collection events will be raised in SetItem + SetItem(IndexOfKey(item.Id), item); + return; + } + } + else + { + var key = GetKeyForItem(item); + if (key != null) + { + var exists = Contains(key); if (exists) { - var keyExists = Contains(item.Name); - if (keyExists) - throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates"); - //collection events will be raised in SetItem - SetItem(IndexOfKey(item.Id), item); + SetItem(IndexOfKey(key), item); return; } } - else - { - var key = GetKeyForItem(item); - if (key != null) - { - var exists = Contains(key); - if (exists) - { - //collection events will be raised in SetItem - SetItem(IndexOfKey(key), item); - return; - } - } - } - //collection events will be raised in InsertItem - base.Add(item); - } - finally - { - if (_addLocker.IsWriteLockHeld) - _addLocker.ExitWriteLock(); } + //collection events will be raised in InsertItem + base.Add(item); } /// diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index cc9d00fa5d..b7a735e5dc 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; -using System.Threading; namespace Umbraco.Core.Models { @@ -16,10 +15,6 @@ namespace Umbraco.Core.Models // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { - [IgnoreDataMember] - private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - - internal PropertyTypeCollection(bool supportsPublishing) { SupportsPublishing = supportsPublishing; @@ -87,36 +82,28 @@ namespace Umbraco.Core.Models item.SupportsPublishing = SupportsPublishing; // TODO: this is not pretty and should be refactored - try - { - _addLocker.EnterWriteLock(); - var key = GetKeyForItem(item); - if (key != null) - { - var exists = Contains(key); - if (exists) - { - //collection events will be raised in SetItem - SetItem(IndexOfKey(key), item); - return; - } - } - //check if the item's sort order is already in use - if (this.Any(x => x.SortOrder == item.SortOrder)) - { - //make it the next iteration - item.SortOrder = this.Max(x => x.SortOrder) + 1; - } - - //collection events will be raised in InsertItem - base.Add(item); - } - finally + var key = GetKeyForItem(item); + if (key != null) { - if (_addLocker.IsWriteLockHeld) - _addLocker.ExitWriteLock(); + var exists = Contains(key); + if (exists) + { + //collection events will be raised in SetItem + SetItem(IndexOfKey(key), item); + return; + } } + + //check if the item's sort order is already in use + if (this.Any(x => x.SortOrder == item.SortOrder)) + { + //make it the next iteration + item.SortOrder = this.Max(x => x.SortOrder) + 1; + } + + //collection events will be raised in InsertItem + base.Add(item); } /// From b75a7865519bd27f311ab7bba9746864d969dd32 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 16:41:11 +1000 Subject: [PATCH 02/92] Ensures the ReaderWriterLockSlim within our caches are disposed at the end of the app --- src/Umbraco.Core/Cache/AppCaches.cs | 25 ++++++++++++++++- .../Cache/AppPolicedCacheDictionary.cs | 27 +++++++++++++++++-- src/Umbraco.Core/Cache/DeepCloneAppCache.cs | 23 +++++++++++++++- src/Umbraco.Core/Cache/ObjectCacheAppCache.cs | 21 ++++++++++++++- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 22 ++++++++++++++- 5 files changed, 112 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Cache/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs index fbfc4c8c82..05453663f5 100644 --- a/src/Umbraco.Core/Cache/AppCaches.cs +++ b/src/Umbraco.Core/Cache/AppCaches.cs @@ -6,8 +6,10 @@ namespace Umbraco.Core.Cache /// /// Represents the application caches. /// - public class AppCaches + public class AppCaches : IDisposable { + private bool _disposedValue; + /// /// Initializes a new instance of the for use in a web application. /// @@ -82,5 +84,26 @@ namespace Umbraco.Core.Cache /// search through all keys on a global scale. /// public IsolatedCaches IsolatedCaches { get; } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + RuntimeCache.DisposeIfDisposable(); + RequestCache.DisposeIfDisposable(); + IsolatedCaches.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs index 5c60dededa..4a6c032e4f 100644 --- a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs +++ b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Cache /// Provides a base class for implementing a dictionary of . /// /// The type of the dictionary key. - public abstract class AppPolicedCacheDictionary + public abstract class AppPolicedCacheDictionary : IDisposable { private readonly ConcurrentDictionary _caches = new ConcurrentDictionary(); @@ -24,6 +24,7 @@ namespace Umbraco.Core.Cache /// Gets the internal cache factory, for tests only! /// internal readonly Func CacheFactory; + private bool _disposedValue; /// /// Gets or creates a cache. @@ -70,5 +71,27 @@ namespace Umbraco.Core.Cache foreach (var cache in _caches.Values) cache.Clear(); } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + foreach(var value in _caches.Values) + { + value.DisposeIfDisposable(); + } + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs index eff06e2aad..10a83d7423 100644 --- a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -12,8 +12,10 @@ namespace Umbraco.Core.Cache /// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item, /// when the item is deep-cloneable. /// - internal class DeepCloneAppCache : IAppPolicyCache + internal class DeepCloneAppCache : IAppPolicyCache, IDisposable { + private bool _disposedValue; + /// /// Initializes a new instance of the class. /// @@ -153,5 +155,24 @@ namespace Umbraco.Core.Cache return input; } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + InnerCache.DisposeIfDisposable(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index 5c4f76f51d..fd1e7f21f1 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -13,9 +13,10 @@ namespace Umbraco.Core.Cache /// /// Implements on top of a . /// - public class ObjectCacheAppCache : IAppPolicyCache + public class ObjectCacheAppCache : IAppPolicyCache, IDisposable { private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + private bool _disposedValue; /// /// Initializes a new instance of the . @@ -360,5 +361,23 @@ namespace Umbraco.Core.Cache } return policy; } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _locker.Dispose(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index bec596b129..d7377a193a 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Cache /// Implements on top of a . /// /// The underlying cache is expected to be HttpRuntime.Cache. - internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache + internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache, IDisposable { // locker object that supports upgradeable read locking // does not need to support recursion if we implement the cache correctly and ensure @@ -19,6 +19,7 @@ namespace Umbraco.Core.Cache private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); private readonly System.Web.Caching.Cache _cache; + private bool _disposedValue; /// /// Initializes a new instance of the class. @@ -204,5 +205,24 @@ namespace Umbraco.Core.Cache _locker.ExitWriteLock(); } } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _locker.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } From 7e332b23c9852a827f8ba7691ed2fc3161d0d07c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 17:14:34 +1000 Subject: [PATCH 03/92] Fixes ConcurrentHashSet to just use a ConcurrentDictionary as it's underlying source and remove the ReaderWriterLockSlim since we are never disposing this. Makes IdkMap disposable. --- .../Collections/ConcurrentHashSet.cs | 109 +++--------------- src/Umbraco.Core/Services/IdkMap.cs | 29 ++++- 2 files changed, 41 insertions(+), 97 deletions(-) diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index c4dba51acd..0424c4dae1 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,8 +1,8 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; namespace Umbraco.Core.Collections { @@ -14,9 +14,10 @@ namespace Umbraco.Core.Collections [Serializable] public class ConcurrentHashSet : ICollection { - private readonly HashSet _innerSet = new HashSet(); - private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - + // Internally we just use a ConcurrentDictionary with the same value for the Value (since we don't actually care about the value) + private static readonly byte _emptyValue = 0x0; + private readonly ConcurrentDictionary _innerSet = new ConcurrentDictionary(); + /// /// Returns an enumerator that iterates through the collection. /// @@ -26,7 +27,7 @@ namespace Umbraco.Core.Collections /// 1 public IEnumerator GetEnumerator() { - return GetThreadSafeClone().GetEnumerator(); + return _innerSet.Keys.GetEnumerator(); } /// @@ -50,16 +51,7 @@ namespace Umbraco.Core.Collections /// The object to remove from the .The is read-only. public bool Remove(T item) { - try - { - _instanceLocker.EnterWriteLock(); - return _innerSet.Remove(item); - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + return _innerSet.TryRemove(item, out _); } @@ -74,17 +66,7 @@ namespace Umbraco.Core.Collections { get { - try - { - _instanceLocker.EnterReadLock(); - return _innerSet.Count; - } - finally - { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); - } - + return _innerSet.Count; } } @@ -99,19 +81,10 @@ namespace Umbraco.Core.Collections /// /// Adds an item to the . /// - /// The object to add to the .The is read-only. + /// The object to add to the . public void Add(T item) { - try - { - _instanceLocker.EnterWriteLock(); - _innerSet.Add(item); - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + _innerSet.TryAdd(item, _emptyValue); } /// @@ -121,21 +94,7 @@ namespace Umbraco.Core.Collections /// public bool TryAdd(T item) { - if (Contains(item)) return false; - try - { - _instanceLocker.EnterWriteLock(); - - //double check - if (_innerSet.Contains(item)) return false; - _innerSet.Add(item); - return true; - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + return _innerSet.TryAdd(item, _emptyValue); } /// @@ -144,16 +103,7 @@ namespace Umbraco.Core.Collections /// The is read-only. public void Clear() { - try - { - _instanceLocker.EnterWriteLock(); - _innerSet.Clear(); - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + _innerSet.Clear(); } /// @@ -165,16 +115,7 @@ namespace Umbraco.Core.Collections /// The object to locate in the . public bool Contains(T item) { - try - { - _instanceLocker.EnterReadLock(); - return _innerSet.Contains(item); - } - finally - { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); - } + return _innerSet.ContainsKey(item); } /// @@ -183,24 +124,8 @@ namespace Umbraco.Core.Collections /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(T[] array, int index) { - var clone = GetThreadSafeClone(); - clone.CopyTo(array, index); - } - - private HashSet GetThreadSafeClone() - { - HashSet clone = null; - try - { - _instanceLocker.EnterReadLock(); - clone = new HashSet(_innerSet, _innerSet.Comparer); - } - finally - { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); - } - return clone; + var keys = _innerSet.Keys; + keys.CopyTo(array, index); } /// @@ -209,8 +134,8 @@ namespace Umbraco.Core.Collections /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 public void CopyTo(Array array, int index) { - var clone = GetThreadSafeClone(); - Array.Copy(clone.ToArray(), 0, array, index, clone.Count); + var keys = _innerSet.Keys; + Array.Copy(keys.ToArray(), 0, array, index, keys.Count); } } } diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index f352def49e..b1caa1ea59 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Services { - public class IdkMap + public class IdkMap : IDisposable { private readonly IScopeProvider _scopeProvider; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); @@ -46,6 +46,7 @@ namespace Umbraco.Core.Services private readonly ConcurrentDictionary id2key, Func key2id)> _dictionary = new ConcurrentDictionary id2key, Func key2id)>(); + private bool _disposedValue; internal void SetMapper(UmbracoObjectTypes umbracoObjectType, Func id2key, Func key2id) { @@ -106,8 +107,8 @@ namespace Umbraco.Core.Services } finally { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); } } @@ -125,8 +126,8 @@ namespace Umbraco.Core.Services } finally { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); } } #endif @@ -369,5 +370,23 @@ namespace Umbraco.Core.Services public T Id { get; } } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _locker.Dispose(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } From bf7a3251d80b2855605c6cf9b7c1c1e73712349a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 17:56:55 +1000 Subject: [PATCH 04/92] Ensure more ReaderWriterLockSlim are disposed or converted --- .../PureLiveModelFactory.cs | 27 ++++++++++++-- .../Logging/WebProfilerProvider.cs | 22 ++++-------- .../PublishedCache/NuCache/MemberCache.cs | 22 +++++++++++- .../NuCache/PublishedSnapshot.cs | 1 + .../PublishedContentTypeCache.cs | 23 +++++++++++- src/Umbraco.Web/Routing/SiteDomainHelper.cs | 36 ++++++++++++++----- 6 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index 8ef99383a4..5b5f54cdc8 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -21,7 +21,7 @@ using File = System.IO.File; namespace Umbraco.ModelsBuilder.Embedded { - internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject + internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject, IDisposable { private Assembly _modelsAssembly; private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; @@ -33,6 +33,7 @@ namespace Umbraco.ModelsBuilder.Embedded private int _ver, _skipver; private readonly int _debugLevel; private BuildManager _theBuildManager; + private bool _disposedValue; private readonly Lazy _umbracoServices; // fixme: this is because of circular refs :( private UmbracoServices UmbracoServices => _umbracoServices.Value; @@ -677,11 +678,31 @@ namespace Umbraco.ModelsBuilder.Embedded public void Stop(bool immediate) { - _watcher.EnableRaisingEvents = false; - _watcher.Dispose(); + Dispose(); HostingEnvironment.UnregisterObject(this); } + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _watcher.EnableRaisingEvents = false; + _watcher.Dispose(); + _locker.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } + #endregion } } diff --git a/src/Umbraco.Web/Logging/WebProfilerProvider.cs b/src/Umbraco.Web/Logging/WebProfilerProvider.cs index f38a606745..8dad5f5b96 100755 --- a/src/Umbraco.Web/Logging/WebProfilerProvider.cs +++ b/src/Umbraco.Web/Logging/WebProfilerProvider.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Logging /// internal class WebProfilerProvider : AspNetRequestProvider { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private readonly object _locker = new object(); private MiniProfiler _startupProfiler; private int _first; private volatile BootPhase _bootPhase; @@ -39,8 +39,7 @@ namespace Umbraco.Web.Logging public void BeginBootRequest() { - _locker.EnterWriteLock(); - try + lock (_locker) { if (_bootPhase != BootPhase.Boot) throw new InvalidOperationException("Invalid boot phase."); @@ -48,28 +47,19 @@ namespace Umbraco.Web.Logging // assign the profiler to be the current MiniProfiler for the request // is's already active, starting and all - HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; - } - finally - { - _locker.ExitWriteLock(); + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; } } public void EndBootRequest() { - _locker.EnterWriteLock(); - try + lock (_locker) { if (_bootPhase != BootPhase.BootRequest) - throw new InvalidOperationException("Invalid boot phase."); + throw new InvalidOperationException("Invalid boot phase."); _bootPhase = BootPhase.Booted; - _startupProfiler = null; - } - finally - { - _locker.ExitWriteLock(); + _startupProfiler = null; } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs index 2e196f629e..f311a7f302 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -13,7 +13,7 @@ using Umbraco.Web.PublishedCache.NuCache.Navigable; namespace Umbraco.Web.PublishedCache.NuCache { - internal class MemberCache : IPublishedMemberCache, INavigableData + internal class MemberCache : IPublishedMemberCache, INavigableData, IDisposable { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; @@ -23,6 +23,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IMemberService _memberService; private readonly PublishedContentTypeCache _contentTypeCache; private readonly bool _previewDefault; + private bool _disposedValue; public MemberCache(bool previewDefault, IAppCache snapshotCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IEntityXmlSerializer entitySerializer) @@ -158,6 +159,25 @@ namespace Umbraco.Web.PublishedCache.NuCache return _contentTypeCache.Get(PublishedItemType.Member, alias); } + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _contentTypeCache.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } + #endregion } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs index 3f5c1aa4d5..85bad38ac7 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs @@ -32,6 +32,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { ContentCache.Dispose(); MediaCache.Dispose(); + MemberCache.Dispose(); } } diff --git a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs index 0f6e9af6bd..c9db02f091 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.PublishedCache /// Represents a content type cache. /// /// This cache is not snapshotted, so it refreshes any time things change. - public class PublishedContentTypeCache + public class PublishedContentTypeCache : IDisposable { // NOTE: These are not concurrent dictionaries because all access is done within a lock private readonly Dictionary _typesByAlias = new Dictionary(); @@ -320,6 +320,8 @@ namespace Umbraco.Web.PublishedCache // for unit tests - changing the callback must reset the cache obviously // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id private Func _getPublishedContentTypeById; + private bool _disposedValue; + internal Func GetPublishedContentTypeById { get => _getPublishedContentTypeById; @@ -367,5 +369,24 @@ namespace Umbraco.Web.PublishedCache { return GetAliasKey(contentType.ItemType, contentType.Alias); } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _lock.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Web/Routing/SiteDomainHelper.cs b/src/Umbraco.Web/Routing/SiteDomainHelper.cs index 6173dfb43c..26937b2899 100644 --- a/src/Umbraco.Web/Routing/SiteDomainHelper.cs +++ b/src/Umbraco.Web/Routing/SiteDomainHelper.cs @@ -4,13 +4,14 @@ using System.Linq; using System.Threading; using System.Text.RegularExpressions; using Umbraco.Core; +using System.ComponentModel; namespace Umbraco.Web.Routing { /// /// Provides utilities to handle site domains. /// - public class SiteDomainHelper : ISiteDomainHelper + public class SiteDomainHelper : ISiteDomainHelper, IDisposable { #region Configure @@ -18,6 +19,7 @@ namespace Umbraco.Web.Routing private static Dictionary _sites; private static Dictionary> _bindings; private static Dictionary> _qualifiedSites; + private bool _disposedValue; // these are for unit tests *only* // ReSharper disable ConvertToAutoPropertyWithPrivateSetter @@ -30,16 +32,10 @@ namespace Umbraco.Web.Routing private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); - /// - /// Returns a disposable object that represents safe write access to config. - /// - /// Should be used in a using(SiteDomainHelper.ConfigWriteLock) { ... } mode. + [EditorBrowsable(EditorBrowsableState.Never)] protected static IDisposable ConfigWriteLock => new WriteLock(ConfigLock); - /// - /// Returns a disposable object that represents safe read access to config. - /// - /// Should be used in a using(SiteDomainHelper.ConfigWriteLock) { ... } mode. + [EditorBrowsable(EditorBrowsableState.Never)] protected static IDisposable ConfigReadLock => new ReadLock(ConfigLock); /// @@ -313,6 +309,28 @@ namespace Umbraco.Web.Routing return ret; } + #endregion + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // This is pretty nasty disposing a static on an instance but it's because this whole class + // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. + ConfigLock.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } From dc089970408355eb7a34d8e3901e036caa070fd5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 18:02:23 +1000 Subject: [PATCH 05/92] Removes usages of UpgradeableReadLock --- src/Umbraco.Core/Cache/ObjectCacheAppCache.cs | 23 +++++++++++++--- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 27 ++++++++++++++----- src/Umbraco.Core/ReadLock.cs | 13 +-------- src/Umbraco.Core/UpgradeableReadLock.cs | 10 +------ src/Umbraco.Core/WriteLock.cs | 13 +-------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index fd1e7f21f1..e2f6017608 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -110,19 +110,34 @@ namespace Umbraco.Core.Cache Lazy result; - using (var lck = new UpgradeableReadLock(_locker)) + try { + _locker.EnterUpgradeableReadLock(); + result = MemoryCache.Get(key) as Lazy; if (result == null || FastDictionaryAppCacheBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { result = FastDictionaryAppCacheBase.GetSafeLazy(factory); var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); - lck.UpgradeToWriteLock(); - //NOTE: This does an add or update - MemoryCache.Set(key, result, policy); + try + { + _locker.EnterWriteLock(); + //NOTE: This does an add or update + MemoryCache.Set(key, result, policy); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } } } + finally + { + if (_locker.IsUpgradeableReadLockHeld) + _locker.ExitUpgradeableReadLock(); + } //return result.Value; diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index d7377a193a..671cd2d9c4 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -139,8 +139,10 @@ namespace Umbraco.Core.Cache var value = result == null ? null : GetSafeLazyValue(result); if (value != null) return value; - using (var lck = new UpgradeableReadLock(_locker)) + try { + _locker.EnterUpgradeableReadLock(); + result = _cache.Get(key) as Lazy; // null if key not found // cannot create value within the lock, so if result.IsValueCreated is false, just @@ -153,15 +155,28 @@ namespace Umbraco.Core.Cache var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); - lck.UpgradeToWriteLock(); + try + { + _locker.EnterWriteLock(); - // create a cache dependency if one is needed. - var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; - //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! + _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } } } + finally + { + if (_locker.IsUpgradeableReadLockHeld) + _locker.ExitUpgradeableReadLock(); + } // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache // exceptions (but try again and again) and silently eat them - however at diff --git a/src/Umbraco.Core/ReadLock.cs b/src/Umbraco.Core/ReadLock.cs index 9d3ef22168..f830d08bfa 100644 --- a/src/Umbraco.Core/ReadLock.cs +++ b/src/Umbraco.Core/ReadLock.cs @@ -1,20 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; namespace Umbraco.Core { - /// - /// Provides a convenience methodology for implementing locked access to resources. - /// - /// - /// Intended as an infrastructure class. - /// This is a very inefficient way to lock as it allocates one object each time we lock, - /// so it's OK to use this class for things that happen once, where it is convenient, but not - /// for performance-critical code! - /// + [Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")] public class ReadLock : IDisposable { private readonly ReaderWriterLockSlim _rwLock; diff --git a/src/Umbraco.Core/UpgradeableReadLock.cs b/src/Umbraco.Core/UpgradeableReadLock.cs index e3717fdf9a..656470cd8a 100644 --- a/src/Umbraco.Core/UpgradeableReadLock.cs +++ b/src/Umbraco.Core/UpgradeableReadLock.cs @@ -1,17 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; namespace Umbraco.Core { - /// - /// Provides a convenience methodology for implementing locked access to resources. - /// - /// - /// Intended as an infrastructure class. - /// + [Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")] public class UpgradeableReadLock : IDisposable { private readonly ReaderWriterLockSlim _rwLock; diff --git a/src/Umbraco.Core/WriteLock.cs b/src/Umbraco.Core/WriteLock.cs index dfa170218b..1b698dc59c 100644 --- a/src/Umbraco.Core/WriteLock.cs +++ b/src/Umbraco.Core/WriteLock.cs @@ -1,20 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; namespace Umbraco.Core { - /// - /// Provides a convenience methodology for implementing locked access to resources. - /// - /// - /// Intended as an infrastructure class. - /// This is a very inefficient way to lock as it allocates one object each time we lock, - /// so it's OK to use this class for things that happen once, where it is convenient, but not - /// for performance-critical code! - /// + [Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")] public class WriteLock : IDisposable { private readonly ReaderWriterLockSlim _rwLock; From 5d8fb9670bd75b4a002017b0dcd849c1fa159497 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Tue, 29 Jun 2021 00:37:19 +0200 Subject: [PATCH 06/92] Textstring prevalue editor view: Set id attribute (#10453) * Add missing focus styling * Set id attribute Co-authored-by: BatJan Co-authored-by: Jan Skovgaard Olsen --- .../src/views/prevalueeditors/textstring.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html index 717b9a8ce2..5d613881b1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From a19b79553d16dad705c62804df9adcad7312bac8 Mon Sep 17 00:00:00 2001 From: David Challener <694674+dchallener@users.noreply.github.com> Date: Tue, 29 Jun 2021 16:12:45 +0100 Subject: [PATCH 07/92] Update contributing.md Visual Studio min version Having had an older version of visual studio running I've been unable to build the source without making changes to the solution. However, upgrading to at least VS 16.8 has meant those changes haven't been required. This is to do with how the language target "latest" was working. Propose upping the min version listed here to avoid build issues being raised. --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3432ac472a..bada94c30b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -136,7 +136,7 @@ You can get in touch with [the core contributors team](#the-core-contributors-te In order to build the Umbraco source code locally, first make sure you have the following installed. - * [Visual Studio 2019 v16.3+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/) + * [Visual Studio 2019 v16.8+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/) * [Node.js v10+](https://nodejs.org/en/download/) * npm v6.4.1+ (installed with Node.js) * [Git command line](https://git-scm.com/download/) From 33e44181c1d044f4c5a04a3a495290725df69d2a Mon Sep 17 00:00:00 2001 From: David Challener <694674+dchallener@users.noreply.github.com> Date: Tue, 29 Jun 2021 22:05:18 +0100 Subject: [PATCH 08/92] Disable cached partials if in preview mode We don't want unpublished items in cached views - possible leak to live site. Show previews using normal partial view instead. --- src/Umbraco.Web/CacheHelperExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs index ae8df63415..e3b96106db 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -37,7 +37,8 @@ namespace Umbraco.Web ViewDataDictionary viewData = null) { //disable cached partials in debug mode: http://issues.umbraco.org/issue/U4-5940 - if (htmlHelper.ViewContext.HttpContext.IsDebuggingEnabled) + //disable cached partials in preview mode: https://github.com/umbraco/Umbraco-CMS/issues/10384 + if (htmlHelper.ViewContext.HttpContext.IsDebuggingEnabled || Umbraco.Web.Composing.Current.UmbracoContext.InPreviewMode) { // just return a normal partial view instead return htmlHelper.Partial(partialViewName, model, viewData); From 7afcd590374e18c7232f38d913c1ee9d51177992 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 1 Jul 2021 13:48:56 +0100 Subject: [PATCH 09/92] Fixed "user-dialog" dashboard markup The logic in the markup was previously incorrect. I changed the check from `tab.length` to `dashboard.length`. The custom dashboard's label isn't `.caption` and it's per dashboard, not per "property". Lastly, `.path` should be `.view`. Resolves #6417 --- .../src/views/common/overlays/user/user.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index fdd2671200..2de04a0147 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -130,13 +130,11 @@ -
+
+
{{tab.label}}
-
-

{{property.caption}}

-
-
+
From d6a27d6646de5454e6480a1f7d1e22a63034a650 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Thu, 1 Jul 2021 23:58:59 +0200 Subject: [PATCH 10/92] Color picker: Fix label view (#10445) * Add missing focus styling * Change width to ensure label does not get too wide Co-authored-by: BatJan Co-authored-by: Jan Skovgaard Olsen --- .../src/less/components/umb-color-swatches.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index 8cf64e183c..bdfc55f648 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -59,7 +59,7 @@ font-size: 14px; padding: 1px 5px; min-height: 45px; - max-width: 100%; + max-width: calc(100% - 8px); margin-top: auto; margin-bottom: -3px; margin-left: -1px; From e67d59a789995fc95cb0f0a30233c9bf0c0899e2 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 22 Mar 2021 18:15:39 +0100 Subject: [PATCH 11/92] Adjust showLabels to be parsed as boolean --- .../src/views/propertyeditors/boolean/boolean.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js index 018c2b72c1..c93b558a23 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js @@ -11,8 +11,8 @@ function booleanEditorController($scope) { showLabels: false }; - if ($scope.model.config && $scope.model.config.showLabels && Object.toBoolean($scope.model.config.showLabels)) { - config.showLabels = true; + if ($scope.model.config) { + $scope.model.config.showLabels = $scope.model.config.showLabels ? Object.toBoolean($scope.model.config.showLabels) : config.showLabels; } // Map the user config From 3e208904356f1fe1262a7edee052dbf9663a6ad3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 2 Jul 2021 11:47:39 +0200 Subject: [PATCH 12/92] Fix dependencies to make 8.15 installable via NuGet --- build/NuSpecs/UmbracoCms.Web.nuspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index e96a217f4e..7aebfae108 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -43,7 +43,8 @@ - + + From 1e54d0551438b7ec97072b5fb14551163d17a81f Mon Sep 17 00:00:00 2001 From: Henk Jan Pluim Date: Tue, 6 Jul 2021 16:46:07 +0200 Subject: [PATCH 13/92] #10577 bugfix - Umbraco backoffice allows you to assign the same 'login' on two different Members (#10591) --- src/Umbraco.Core/Services/Implement/MemberService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 877bb5c9ce..b26a6bef39 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -626,7 +626,7 @@ namespace Umbraco.Core.Services.Implement query.Where(member => member.Username.EndsWith(login)); break; case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + query.Where(member => member.Username.SqlWildcard(login, TextColumnType.NVarchar)); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); From a1e0af6fff971af3270b6292b0be38a6988d3a70 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 6 Jul 2021 18:39:56 +0100 Subject: [PATCH 14/92] Accessibility improvements for Extensions in Razor Markup (#9576) * Support for language of parts * Changed casing of "FallBack" to "Fallback" to be consistent with the existing code. * Now tests the type to determine if span should be added. * Remove magic strings and return
for IHtmlString values This fixes the "string" vs "String" issue in the previous commit and prevents the function returning invalid markup * Fix for test providing string as "object" Co-authored-by: Joe Glombek --- .../Models/PublishedContent/Fallback.cs | 6 ++ .../PublishedContentLanguageVariantTests.cs | 8 ++- .../PublishedValueFallback.cs | 65 ++++++++++++++++--- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs index 0434218555..805f14d21b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs @@ -71,5 +71,11 @@ namespace Umbraco.Core.Models.PublishedContent { return GetEnumerator(); } + + public const int DisplayFallbackLanguage = 4; + /// + /// Gets the fallback to tree ancestors policy. + /// + public static Fallback ToDisplayFallbackLanguage => new Fallback(new[] { DisplayFallbackLanguage }); } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 636f8502ed..05cf8d01c1 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -235,7 +235,13 @@ namespace Umbraco.Tests.PublishedContent var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); Assert.AreEqual("Welcome", value); } - + [Test] + public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_And_Language_Change() + { + var content = Current.UmbracoContext.Content.GetAtRoot().First(); + var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.DisplayFallbackLanguage)); + Assert.AreEqual("Welcome", value); + } [Test] public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() { diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 57c3094eaf..b5901fef69 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Web; +using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -35,6 +37,8 @@ namespace Umbraco.Web.Models.PublishedContent { _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); + var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); + foreach (var f in fallback) { switch (f) @@ -45,9 +49,11 @@ namespace Umbraco.Web.Models.PublishedContent value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(property, culture, segment, out value)) + if (TryGetValueWithLanguageFallback(property, culture, segment, out value, includeFallbackLanguage)) return true; break; + case Fallback.DisplayFallbackLanguage: + continue; default: throw NotSupportedFallbackMethod(f, "property"); } @@ -67,6 +73,7 @@ namespace Umbraco.Web.Models.PublishedContent public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) { var propertyType = content.ContentType.GetPropertyType(alias); + var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); if (propertyType == null) { value = default; @@ -85,9 +92,11 @@ namespace Umbraco.Web.Models.PublishedContent value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) return true; break; + case Fallback.DisplayFallbackLanguage: + continue; default: throw NotSupportedFallbackMethod(f, "element"); } @@ -107,6 +116,7 @@ namespace Umbraco.Web.Models.PublishedContent public virtual bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty) { noValueProperty = default; + var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) @@ -131,13 +141,15 @@ namespace Umbraco.Web.Models.PublishedContent case Fallback.Language: if (propertyType == null) continue; - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) return true; break; case Fallback.Ancestors: - if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty)) + if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty, includeFallbackLanguage)) return true; break; + case Fallback.DisplayFallbackLanguage: + continue; default: throw NotSupportedFallbackMethod(f, "content"); } @@ -155,9 +167,10 @@ namespace Umbraco.Web.Models.PublishedContent // tries to get a value, recursing the tree // because we recurse, content may not even have the a property with the specified alias (but only some ancestor) // in case no value was found, noValueProperty contains the first property that was found (which does not have a value) - private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty) + private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty, bool includeFallbackLanguage) { IPublishedProperty property; // if we are here, content's property has no value + var originalCulture = culture; do { content = content.Parent; @@ -183,6 +196,10 @@ namespace Umbraco.Web.Models.PublishedContent if (property != null && property.HasValue(culture, segment)) { value = property.Value(culture, segment); + if (includeFallbackLanguage && originalCulture != culture) + { + value = GetMarkUpForFallbackLanguage(culture, value); + } return true; } @@ -191,7 +208,7 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value, bool includeFallbackLanguage) { value = default; @@ -217,6 +234,10 @@ namespace Umbraco.Web.Models.PublishedContent if (property.HasValue(culture2, segment)) { value = property.Value(culture2, segment); + if (includeFallbackLanguage && culture2 != culture) + { + value = GetMarkUpForFallbackLanguage(culture2, value); + } return true; } @@ -225,7 +246,7 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) { value = default; @@ -251,6 +272,10 @@ namespace Umbraco.Web.Models.PublishedContent if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); + if (includeFallbackLanguage && culture2 != culture) + { + value = GetMarkUpForFallbackLanguage(culture2, value); + } return true; } @@ -259,7 +284,7 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) { value = default; @@ -288,11 +313,35 @@ namespace Umbraco.Web.Models.PublishedContent if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); + if (includeFallbackLanguage && culture2 != culture) + { + value = GetMarkUpForFallbackLanguage(culture2, value); + + } return true; } language = language2; } } + + private T GetMarkUpForFallbackLanguage(string culture2, T value) + { + var typeOfT = typeof(T); + if (value is string) + { + var newValue = "" + value + ""; + return (T)Convert.ChangeType(newValue, typeof(T)); + } + else if (typeOfT == typeof(IHtmlString)) + { + // we want to return a block element here since the IHtmlString could contain futher block elements + var newValue = "
" + value + "
"; + IHtmlString htmlString = new MvcHtmlString(newValue); + return (T)htmlString; + } + + return value; + } } } From 87a7e84cbabdf5f692e52f2d73c6b474092bfaab Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Tue, 13 Jul 2021 09:57:20 +0200 Subject: [PATCH 15/92] Bugfix: #10414 - Validation message doesn't disappear once the issue is fixed (#10581) * Add missing focus styling * Fix issue where validation message does not disappear when all chars are removed Co-authored-by: BatJan Co-authored-by: Jan Skovgaard Olsen --- .../src/views/propertyeditors/textbox/textbox.controller.js | 4 ++++ .../src/views/propertyeditors/textbox/textbox.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index b7c740e749..3e4539c6ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -31,6 +31,10 @@ function textboxController($scope, validationMessageService) { checkLengthVadility(); $scope.nearMaxLimit = $scope.validLength && $scope.charsCount > Math.max($scope.maxChars*.8, $scope.maxChars-25); } + else { + $scope.charsCount = 0; + checkLengthVadility(); + } } $scope.model.onValueChanged = $scope.change; $scope.change(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 5e135ea7d9..1f1131c43f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -10,7 +10,7 @@ aria-required="{{model.validation.mandatory}}" aria-invalid="False" ng-trim="false" - ng-keyup="change()" /> + ng-change="change()" />

{{model.label}} {{textboxFieldForm.textbox.errorMsg}}

From c812dab96739c5cfc228c70796af0c2b5b001fec Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 23 Jul 2021 01:26:37 +0200 Subject: [PATCH 16/92] File upload checkered background (#10621) * Use umb-icon component * Cleanup unused css and use checkered background on both image cropper and file upload --- .../components/umb-property-file-upload.less | 2 +- .../src/less/property-editors.less | 349 +++++++++--------- .../upload/umb-property-file-upload.html | 2 +- 3 files changed, 170 insertions(+), 183 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less index 83774e2dae..75d171dd87 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less @@ -20,7 +20,7 @@ } } - i.icon { + .icon { font-size: 55px; line-height: 70px } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 11d11c7e3a..87603671dd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -575,43 +575,31 @@ font-size: 22px; } -/* - .umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder { - display: inline-block; - max-width: 100%; +.umb-crop-thumbnail-container { + img { + max-width: unset; } +} - .umb-cropper-imageholder { - float: left; - } +.cropList { + display: inline-block; + position: relative; + vertical-align: top; + flex:0; +} - .umb-cropper-imageholder umb-image-gravity { - display:block; - } - */ +.umb-fileupload, +.umb-cropper-gravity { - .umb-crop-thumbnail-container { - img { - max-width: unset; - } - } - - .cropList { - display: inline-block; - position: relative; - vertical-align: top; - flex:0; - } - - .umb-cropper-gravity .gravity-container { + .gravity-container { border: 1px solid @inputBorder; - box-sizing: border-box; - line-height: 0; width: 100%; height: 100%; overflow: hidden; - .checkeredBackground(); + box-sizing: border-box; + line-height: 0; contain: content; + .checkeredBackground(); &:focus, &:focus-within { border-color: @inputBorderFocus; @@ -621,7 +609,6 @@ position: relative; width: 100%; height: 100%; - display: flex; justify-content: center; align-items: center; @@ -637,169 +624,169 @@ } } } - +} - .umb-cropper-gravity img { - position: relative; - max-width: 100%; - height: auto; - top: 0; - left: 0; - } +.umb-cropper-gravity img { + position: relative; + max-width: 100%; + height: auto; + top: 0; + left: 0; +} - .umb-cropper-gravity .overlayViewport { - position: absolute; - top:0; - bottom:0; - left:0; - right:0; - contain: strict; +.umb-cropper-gravity .overlayViewport { + position: absolute; + top:0; + bottom:0; + left:0; + right:0; + contain: strict; - display: flex; - justify-content: center; - align-items: center; - } - .umb-cropper-gravity .overlay { - position: relative; - display: block; - max-width: 100%; - max-height: 100%; - cursor: crosshair; - } - .umb-cropper-gravity .overlay .focalPoint { - position: absolute; - top: 0; - left: 0; - cursor: move; - z-index: @zindexCropperOverlay; + display: flex; + justify-content: center; + align-items: center; +} +.umb-cropper-gravity .overlay { + position: relative; + display: block; + max-width: 100%; + max-height: 100%; + cursor: crosshair; +} +.umb-cropper-gravity .overlay .focalPoint { + position: absolute; + top: 0; + left: 0; + cursor: move; + z-index: @zindexCropperOverlay; - width: 14px; - height: 14px; - // this element should have no width or height as its preventing the jQuery draggable-plugin to go all the way to the sides: - margin-left: -10px; - margin-top: -10px; - margin-right: -10px; - margin-bottom: -10px; + width: 14px; + height: 14px; + // this element should have no width or height as its preventing the jQuery draggable-plugin to go all the way to the sides: + margin-left: -10px; + margin-top: -10px; + margin-right: -10px; + margin-bottom: -10px; - text-align: center; - border-radius: 20px; - background: @pinkLight; - border: 3px solid @white; - opacity: 0.8; - } + text-align: center; + border-radius: 20px; + background: @pinkLight; + border: 3px solid @white; + opacity: 0.8; +} - .umb-cropper-gravity .overlay .focalPoint i { - font-size: 26px; - line-height: 26px; - opacity: 0.8 !important; - } +.umb-cropper-gravity .overlay .focalPoint i { + font-size: 26px; + line-height: 26px; + opacity: 0.8 !important; +} - .imagecropper { - display: flex; - align-items: flex-start; - flex-direction: row; +.imagecropper { + display: flex; + align-items: flex-start; + flex-direction: row; - @media (max-width: 768px) { - flex-direction: column; - } - - } - - .imagecropper .umb-cropper__container { - position: relative; - width: 100%; - } - - .imagecropper .umb-cropper__container .button-drawer { - display: flex; - justify-content: flex-end; - padding: 10px; - position: relative; - - button { - margin-left: 4px; - } - } - - .umb-close-cropper { - position: absolute; - top: 3px; - right: 3px; - cursor: pointer; - z-index: @zindexCropperOverlay + 1; - } - - .umb-close-cropper:hover { - opacity: .9; - background: @gray-10; - } - - .imagecropper .umb-sortable-thumbnails { - display: flex; - flex-direction: row; - flex-wrap: wrap; - } - - .imagecropper .umb-sortable-thumbnails li { - display: flex; + @media (max-width: 768px) { flex-direction: column; - justify-content: space-between; - padding: 8px; + } + +} + +.imagecropper .umb-cropper__container { + position: relative; + width: 100%; +} + +.imagecropper .umb-cropper__container .button-drawer { + display: flex; + justify-content: flex-end; + padding: 10px; + position: relative; + + button { + margin-left: 4px; + } +} + +.umb-close-cropper { + position: absolute; + top: 3px; + right: 3px; + cursor: pointer; + z-index: @zindexCropperOverlay + 1; +} + +.umb-close-cropper:hover { + opacity: .9; + background: @gray-10; +} + +.imagecropper .umb-sortable-thumbnails { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.imagecropper .umb-sortable-thumbnails li { + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 8px; + margin-top: 0; +} + +.imagecropper .umb-sortable-thumbnails li.current { + border-color: @gray-8; + background: @gray-10; + color: @black; + cursor: pointer; +} + +.imagecropper .umb-sortable-thumbnails li:hover, +.imagecropper .umb-sortable-thumbnails li.current:hover { + border-color: @gray-8; + background: @gray-10; + color: @black; + cursor: pointer; + opacity: .95; +} + +.imagecropper .umb-sortable-thumbnails li .crop-name, +.imagecropper .umb-sortable-thumbnails li .crop-size, +.imagecropper .umb-sortable-thumbnails li .crop-annotation { + display: block; + text-align: left; + font-size: 13px; + line-height: 1; +} + +.imagecropper .umb-sortable-thumbnails li .crop-name { + font-weight: bold; + margin: 10px 0 5px; +} + +.imagecropper .umb-sortable-thumbnails li .crop-size, +.imagecropper .umb-sortable-thumbnails li .crop-annotation { + font-size: 10px; + font-style: italic; + margin: 0 0 5px; +} + +.imagecropper .umb-sortable-thumbnails li .crop-annotation { + color: @gray-6; +} + +.btn-crop-delete { + display: block; + text-align: left; +} + +.imagecropper .cropList-container { + h5 { + margin-left: 10px; margin-top: 0; } - - .imagecropper .umb-sortable-thumbnails li.current { - border-color: @gray-8; - background: @gray-10; - color: @black; - cursor: pointer; - } - - .imagecropper .umb-sortable-thumbnails li:hover, - .imagecropper .umb-sortable-thumbnails li.current:hover { - border-color: @gray-8; - background: @gray-10; - color: @black; - cursor: pointer; - opacity: .95; - } - - .imagecropper .umb-sortable-thumbnails li .crop-name, - .imagecropper .umb-sortable-thumbnails li .crop-size, - .imagecropper .umb-sortable-thumbnails li .crop-annotation { - display: block; - text-align: left; - font-size: 13px; - line-height: 1; - } - - .imagecropper .umb-sortable-thumbnails li .crop-name { - font-weight: bold; - margin: 10px 0 5px; - } - - .imagecropper .umb-sortable-thumbnails li .crop-size, - .imagecropper .umb-sortable-thumbnails li .crop-annotation { - font-size: 10px; - font-style: italic; - margin: 0 0 5px; - } - - .imagecropper .umb-sortable-thumbnails li .crop-annotation { - color: @gray-6; - } - - .btn-crop-delete { - display: block; - text-align: left; - } - - .imagecropper .cropList-container { - h5 { - margin-left: 10px; - margin-top: 0; - } - } +} // // Folder browser diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html index fadc0ac3b1..1db179aa42 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html @@ -4,7 +4,7 @@
- +

Click to upload

From 6116ec814d930d59d1db7d5e1c87a70b0cef871e Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Tue, 20 Jul 2021 17:29:01 +0200 Subject: [PATCH 17/92] Update umbeditorview.directive.js --- .../directives/components/editor/umbeditorview.directive.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js index 34cb55a794..35981049af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js @@ -59,6 +59,8 @@ Use this directive to construct the main editor window.
  • {@link umbraco.directives.directive:umbEditorContainer umbEditorContainer}
  • {@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}
  • + +@param {boolean} footer Whether the directive should make place for a {@link umbraco.directives.directive:umbEditorFooter umbEditorFooter} at the bottom (`true` by default). **/ (function() { From c86d3b1dae6ea34cc60321c10b39e063daf305fe Mon Sep 17 00:00:00 2001 From: Patrick de Mooij Date: Fri, 16 Oct 2020 12:00:39 +0200 Subject: [PATCH 18/92] 9157: Use the sorting event instead of the saving --- src/Umbraco.Core/Services/Implement/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index af14d7fa69..7e694946ef 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2388,7 +2388,7 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents) { //raise cancelable sorting event - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting))) + if (scope.Events.DispatchCancelable(Sorting, this, saveEventArgs, nameof(Sorting))) return OperationResult.Cancel(evtMsgs); //raise saving event (this one cannot be canceled) From 2eb554f1b46d50ae8445dcdedc2d759fcb9178da Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 14:52:01 +0200 Subject: [PATCH 19/92] Inject windowResizeListener in Image Crop directive (#10745) --- .../components/imaging/umbimagecrop.directive.js | 12 ++++++------ .../common/services/windowresizelistener.service.js | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js index 60ba71d7a5..1f5d506bdd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js @@ -6,7 +6,7 @@ **/ angular.module("umbraco.directives") .directive('umbImageCrop', - function ($timeout, $window, cropperHelper) { + function ($timeout, cropperHelper, windowResizeListener) { const MAX_SCALE = 4; @@ -26,7 +26,7 @@ angular.module("umbraco.directives") forceUpdate: '@?' }, - link: function (scope, element, attrs, windowResizeListener) { + link: function (scope, element, attrs) { var unsubscribe = []; let sliderRef = null; @@ -72,7 +72,7 @@ angular.module("umbraco.directives") }; function updateSlider() { - if(sliderRef) { + if (sliderRef) { // Update slider range min/max sliderRef.noUiSlider.updateOptions({ "range": { @@ -102,7 +102,7 @@ angular.module("umbraco.directives") // cross-browser wheel delta var delta = Math.max(-50, Math.min(50, (event.wheelDelta || -event.detail))); - if(sliderRef) { + if (sliderRef) { var currentScale =sliderRef.noUiSlider.get(); var newScale = Math.min(Math.max(currentScale + delta*.001*scope.dimensions.image.ratio, scope.dimensions.scale.min), scope.dimensions.scale.max); @@ -127,8 +127,8 @@ angular.module("umbraco.directives") 'left': (parseInt(scope.dimensions.margin.left, 10)) + 'px' } }; - updateStyles(); + updateStyles(); //elements var $viewport = element.find(".viewport"); @@ -138,11 +138,11 @@ angular.module("umbraco.directives") $overlay.bind("focus", function () { $overlay.bind("DOMMouseScroll mousewheel onmousewheel", onScroll); }); + $overlay.bind("blur", function () { $overlay.unbind("DOMMouseScroll mousewheel onmousewheel", onScroll); }); - //default constraints for drag n drop var constraints = { left: { max: 0, min: 0 }, top: { max: 0, min: 0 } }; scope.constraints = constraints; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js index 68691b8629..3c13228a9b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js @@ -11,7 +11,7 @@ */ function windowResizeListener($rootScope) { - var WinReszier = (function () { + var WinResizer = (function () { var registered = []; var inited = false; var resize = _.debounce(function(ev) { @@ -51,7 +51,7 @@ function windowResizeListener($rootScope) { * @param {Function} cb */ register: function (cb) { - WinReszier.register(cb); + WinResizer.register(cb); }, /** @@ -59,9 +59,9 @@ function windowResizeListener($rootScope) { * @param {Function} cb */ unregister: function(cb) { - WinReszier.unregister(cb); + WinResizer.unregister(cb); } }; } -angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); \ No newline at end of file +angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); From 820952561127bb79910928652cf59ee15ea6fbde Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 15:49:16 +0200 Subject: [PATCH 20/92] Modernize mini listview component to use svg icons and `umb-search-filter` component (#10740) --- .../src/less/components/umb-mini-search.less | 13 ++++----- .../views/components/umb-mini-list-view.html | 28 +++++++++---------- .../src/views/components/umb-mini-search.html | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less index ec598c17eb..e4eb8f8235 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less @@ -4,13 +4,12 @@ .icon { position: absolute; - width: 30px; - height: 30px; display: flex; justify-content: center; align-items: center; - margin: 1px; - padding: 0; + font-size: 20px; + margin: 5px; + padding: 1px; pointer-events: none; color: @ui-action-discreet-type; transition: color .1s linear; @@ -30,6 +29,7 @@ .icon { color: @ui-action-discreet-type-hover; } + input { color: @ui-action-discreet-border-hover; border-color: @ui-action-discreet-border-hover; @@ -42,10 +42,9 @@ border-color: @ui-action-discreet-border-hover; cursor: unset; } - + input:focus, &:focus-within input, &.--has-value input { width: 190px; - padding-left:30px; + padding-left: 30px; } - } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index e2319f099d..dfd6f9002c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -5,14 +5,14 @@ ng-repeat="miniListView in miniListViews">
    - +

    {{ miniListView.node.name }}

    @@ -32,19 +32,17 @@
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html index 3c95dbae5b..b69071d4e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html @@ -1,5 +1,5 @@ - + Date: Wed, 28 Jul 2021 22:25:19 +0200 Subject: [PATCH 21/92] Don't squeeze avatar when help panel is open --- .../src/less/components/application/umb-app-header.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index bd1b8ab07a..68a29df89e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -48,6 +48,7 @@ opacity: 0.8; color: @white; font-size: 22px; + flex-shrink: 0; } .umb-app-header__button:hover .umb-app-header__action-icon, From cb06442de44d93b6437fcf4fa92bb4d12dc68f5e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 29 Jul 2021 13:54:05 +0200 Subject: [PATCH 22/92] Fix: Issue when upgraded from 8.14 to 8.15 when inviting user into the backoffice. #10746 (#10767) * Remove unusable method * Fixes #10746 by removing the recursive call ending in an infinite loop Also: the culture is already known, don't take the `CurrentUICulture` --- .../LocalizedTextServiceExtensions.cs | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 2911441578..e062f11306 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -71,30 +71,6 @@ namespace Umbraco.Core.Services #pragma warning restore CS0618 // Type or member is obsolete } - /// - /// Localize using the current thread culture - /// - /// - /// - /// - /// - /// - public static string Localize(this ILocalizedTextService manager, string area, string alias, IDictionary tokens = null) - { - if (manager is ILocalizedTextService2 manager2) - { - return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens); - } - var fullKey = alias; - if (area != null) - { - fullKey = string.Concat(area, "/", alias); - } -#pragma warning disable CS0618 // Type or member is obsolete - return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture, tokens); -#pragma warning restore CS0618 // Type or member is obsolete - } - /// /// Localize a key without any variables /// @@ -108,7 +84,7 @@ namespace Umbraco.Core.Services { if (manager is ILocalizedTextService2 manager2) { - return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens); + return manager2.Localize(area, alias, culture, ConvertToDictionaryVars(tokens)); } var fullKey = alias; if (area != null) From 168abbf2f7597a8c5ccadded19ef619cb9f82e46 Mon Sep 17 00:00:00 2001 From: Robert Ghafoor Date: Fri, 30 Jul 2021 09:34:14 +0200 Subject: [PATCH 23/92] 10758 Caption will now be correctly set when passing in a target to the media picker. --- .../infiniteeditors/mediapicker/mediapicker.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 0b9c59f2da..405556e2ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -168,6 +168,7 @@ angular.module("umbraco") var originalTarget = $scope.target; var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; var altText = $scope.target.altText; + var caption = $scope.target.caption; // ID of a UDI or legacy int ID still could be null/undefinied here // As user may dragged in an image that has not been saved to media section yet @@ -181,6 +182,7 @@ angular.module("umbraco") $scope.target.url = mediaHelper.resolveFileFromEntity(node); $scope.target.thumbnail = mediaHelper.resolveFileFromEntity(node, true); $scope.target.altText = altText; + $scope.target.caption = caption; $scope.target.focalPoint = originalTarget.focalPoint; $scope.target.coordinates = originalTarget.coordinates; openDetailsDialog(); From 56790f72b020eaed5f26fd981a4f1f3feb8a4927 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 2 Aug 2021 00:18:30 +0200 Subject: [PATCH 24/92] Replace angular.forEach with Utilities.forEach (#10759) * Replace angular.forEach with Utilities.forEach * Use localizeMany to localize translations in a single request * Replace angular.forEach * Replace angular.forEach in mocks --- .../components/buttons/umbbutton.directive.js | 30 ++++++------ .../content/umbcontentnodeinfo.directive.js | 14 +++--- .../editor/umbeditornavigation.directive.js | 20 ++++---- .../media/umbmedianodeinfo.directive.js | 21 ++++---- .../components/umbaceeditor.directive.js | 2 +- .../components/umbchildselector.directive.js | 32 ++++++------- .../components/umbgridselector.directive.js | 6 +-- .../components/umbgroupsbuilder.directive.js | 48 +++++++++---------- .../components/umbminilistview.directive.js | 10 ++-- .../components/umbnestedcontent.directive.js | 9 ++-- .../upload/umbfiledropzone.directive.js | 5 +- .../util/disabletabindex.directive.js | 2 +- .../util/umbkeyboardlist.directive.js | 2 +- .../src/common/services/retryqueue.service.js | 2 +- .../dashboard/dashboard.tabs.controller.js | 36 +++++++------- .../test/lib/angular/angular-mocks.js | 32 ++++++------- 16 files changed, 131 insertions(+), 140 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js index dd6fa5c5c8..d1be694fde 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -135,7 +135,7 @@ Use this directive to render an umbraco button. The directive can be used to gen if (vm.buttonStyle) { // make it possible to pass in multiple styles - if(vm.buttonStyle.startsWith("[") && vm.buttonStyle.endsWith("]")) { + if (vm.buttonStyle.startsWith("[") && vm.buttonStyle.endsWith("]")) { // when using an attr it will always be a string so we need to remove square brackets // and turn it into and array @@ -143,16 +143,16 @@ Use this directive to render an umbraco button. The directive can be used to gen // split array by , + make sure to catch whitespaces var array = withoutBrackets.split(/\s?,\s?/g); - angular.forEach(array, function(item){ + Utilities.forEach(array, item => { vm.style = vm.style + " " + "btn-" + item; - if(item === "block") { + if (item === "block") { vm.blockElement = true; } }); } else { vm.style = "btn-" + vm.buttonStyle; - if(vm.buttonStyle === "block") { + if (vm.buttonStyle === "block") { vm.blockElement = true; } } @@ -167,7 +167,7 @@ Use this directive to render an umbraco button. The directive can be used to gen // watch for state changes if (changes.state) { - if(changes.state.currentValue) { + if (changes.state.currentValue) { vm.innerState = changes.state.currentValue; } if (changes.state.currentValue === 'success' || changes.state.currentValue === 'error') { @@ -179,25 +179,25 @@ Use this directive to render an umbraco button. The directive can be used to gen } // watch for disabled changes - if(changes.disabled) { - if(changes.disabled.currentValue) { + if (changes.disabled) { + if (changes.disabled.currentValue) { vm.disabled = changes.disabled.currentValue; } } // watch for label changes - if(changes.label && changes.label.currentValue) { + if (changes.label && changes.label.currentValue) { vm.buttonLabel = changes.label.currentValue; setButtonLabel(); } // watch for label key changes - if(changes.labelKey && changes.labelKey.currentValue) { + if (changes.labelKey && changes.labelKey.currentValue) { setButtonLabel(); } // watch for type changes - if(changes.type) { + if (changes.type) { if (!vm.type) { vm.type = "button";// set the default } @@ -206,23 +206,23 @@ Use this directive to render an umbraco button. The directive can be used to gen } function clickButton(event) { - if(vm.action) { + if (vm.action) { vm.action({$event: event}); } } function setButtonLabel() { // if the button opens a dialog add "..." to the label - if(vm.addEllipsis === "true") { + if (vm.addEllipsis === "true") { vm.buttonLabel = vm.buttonLabel + "..."; } // look up localization key - if(vm.labelKey) { - localizationService.localize(vm.labelKey).then(function(value){ + if (vm.labelKey) { + localizationService.localize(vm.labelKey).then(value => { vm.buttonLabel = value; // if the button opens a dialog add "..." to the label - if(vm.addEllipsis === "true") { + if (vm.addEllipsis === "true") { vm.buttonLabel = vm.buttonLabel + "..."; } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index c20c2a368d..f087f6e084 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -206,11 +206,11 @@ scope.loadingAuditTrail = true; logResource.getPagedEntityLog(scope.auditTrailOptions) - .then(function (data) { + .then(data => { // get current backoffice user and format dates - userService.getCurrentUser().then(function (currentUser) { - angular.forEach(data.items, function (item) { + userService.getCurrentUser().then(currentUser => { + Utilities.forEach(data.items, item => { item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL'); }); }); @@ -232,12 +232,12 @@ function loadRedirectUrls() { scope.loadingRedirectUrls = true; //check if Redirect URL Management is enabled - redirectUrlsResource.getEnableState().then(function (response) { + redirectUrlsResource.getEnableState().then(response => { scope.urlTrackerDisabled = response.enabled !== true; if (scope.urlTrackerDisabled === false) { redirectUrlsResource.getRedirectsForContentItem(scope.node.udi) - .then(function (data) { + .then(data => { scope.redirectUrls = data.searchResults; scope.hasRedirects = (typeof data.searchResults !== 'undefined' && data.searchResults.length > 0); scope.loadingRedirectUrls = false; @@ -250,7 +250,7 @@ } function setAuditTrailLogTypeColor(auditTrail) { - angular.forEach(auditTrail, function (item) { + Utilities.forEach(auditTrail, item => { switch (item.logType) { case "Save": @@ -304,7 +304,7 @@ function formatDatesToLocal() { // get current backoffice user and format dates - userService.getCurrentUser().then(function (currentUser) { + userService.getCurrentUser().then(currentUser => { scope.currentVariant.createDateFormatted = dateHelper.getLocalDate(scope.currentVariant.createDate, currentUser.locale, 'LLL'); scope.currentVariant.releaseDateFormatted = dateHelper.getLocalDate(scope.currentVariant.releaseDate, currentUser.locale, 'LLL'); scope.currentVariant.expireDateFormatted = dateHelper.getLocalDate(scope.currentVariant.expireDate, currentUser.locale, 'LLL'); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js index eec8455969..a912eab609 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js @@ -17,19 +17,19 @@ name: "More" }; - scope.openNavigationItem = function(item) { + scope.openNavigationItem = item => { scope.showDropdown = false; runItemAction(item); setItemToActive(item); - if(scope.onSelect) { + if (scope.onSelect) { scope.onSelect({"item": item}); } eventsService.emit("app.tabChange", item); }; - scope.openAnchorItem = function(item, anchor) { - if(scope.onAnchorSelect) { + scope.openAnchorItem = (item, anchor) => { + if (scope.onAnchorSelect) { scope.onAnchorSelect({"item": item, "anchor": anchor}); } if (item.active !== true) { @@ -37,11 +37,11 @@ } }; - scope.toggleDropdown = function () { + scope.toggleDropdown = () => { scope.showDropdown = !scope.showDropdown; }; - scope.hideDropdown = function() { + scope.hideDropdown = () => { scope.showDropdown = false; }; @@ -60,7 +60,7 @@ function calculateVisibleItems(windowWidth) { // if we don't get a windowWidth stick with the default item limit - if(!windowWidth) { + if (!windowWidth) { return; } @@ -94,7 +94,7 @@ if (selectedItem.view) { // deselect all items - angular.forEach(scope.navigation, function(item, index){ + Utilities.forEach(scope.navigation, item => { item.active = false; }); @@ -112,8 +112,8 @@ } } - var resizeCallback = function(size) { - if(size && size.width) { + var resizeCallback = size => { + if (size && size.width) { calculateVisibleItems(size.width); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index 6f34cfc0a1..c07777ca60 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -25,13 +25,12 @@ scope.memberOptions.entityType = "MEMBER"; scope.hasMemberReferences = false; - function onInit() { - userService.getCurrentUser().then(function(user){ + userService.getCurrentUser().then(user => { // only allow change of media type if user has access to the settings sections - angular.forEach(user.sections, function(section){ - if(section.alias === "settings") { + Utilities.forEach(user.sections, section => { + if (section.alias === "settings") { scope.allowChangeMediaType = true; } }); @@ -52,7 +51,7 @@ function formatDatesToLocal() { // get current backoffice user and format dates - userService.getCurrentUser().then(function (currentUser) { + userService.getCurrentUser().then(currentUser => { scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); scope.node.updateDateFormatted = dateHelper.getLocalDate(scope.node.updateDate, currentUser.locale, 'LLL'); }); @@ -73,20 +72,20 @@ scope.node.extension = mediaHelper.getFileExtension(scope.nodeUrl); } - scope.openMediaType = function (mediaType) { + scope.openMediaType = mediaType => { var editor = { id: mediaType.id, - submit: function(model) { + submit: model => { editorService.close(); }, - close: function() { + close: () => { editorService.close(); } }; editorService.mediaTypeEditor(editor); }; - scope.openSVG = function () { + scope.openSVG = () => { var popup = window.open('', '_blank'); var html = '' + ''; @@ -136,7 +135,7 @@ function loadMediaRelations() { return mediaResource.getPagedReferences(scope.node.id, scope.mediaOptions) - .then(function (data) { + .then(data => { scope.mediaReferences = data; scope.hasMediaReferences = data.items.length > 0; }); @@ -144,7 +143,7 @@ function loadMemberRelations() { return mediaResource.getPagedReferences(scope.node.id, scope.memberOptions) - .then(function (data) { + .then(data => { scope.memberReferences = data; scope.hasMemberReferences = data.items.length > 0; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index ef463e6d95..f36fbc7ea8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -115,7 +115,7 @@ } // onLoad callbacks - angular.forEach(opts.callbacks, function(cb) { + Utilities.forEach(opts.callbacks, cb => { if (Utilities.isFunction(cb)) { cb(acee); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js index 9a841e3e4a..47441326d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js @@ -126,14 +126,14 @@ Use this directive to render a ui component for selecting child items to a paren scope.dialogModel = {}; scope.showDialog = false; - scope.removeChild = function(selectedChild, $index) { - if(scope.onRemove) { + scope.removeChild = (selectedChild, $index) => { + if (scope.onRemove) { scope.onRemove(selectedChild, $index); } }; - scope.addChild = function($event) { - if(scope.onAdd) { + scope.addChild = $event => { + if (scope.onAdd) { scope.onAdd($event); } }; @@ -141,16 +141,16 @@ Use this directive to render a ui component for selecting child items to a paren function syncParentName() { // update name on available item - angular.forEach(scope.availableChildren, function(availableChild){ - if(availableChild.id === scope.parentId) { - availableChild.name = scope.parentName; + Utilities.forEach(scope.availableChildren, availableChild => { + if (availableChild.id === scope.parentId) { + availableChild.name = scope.parentName; } }); // update name on selected child - angular.forEach(scope.selectedChildren, function(selectedChild){ - if(selectedChild.id === scope.parentId) { - selectedChild.name = scope.parentName; + Utilities.forEach(scope.selectedChildren, selectedChild => { + if (selectedChild.id === scope.parentId) { + selectedChild.name = scope.parentName; } }); @@ -159,16 +159,16 @@ Use this directive to render a ui component for selecting child items to a paren function syncParentIcon() { // update icon on available item - angular.forEach(scope.availableChildren, function(availableChild){ - if(availableChild.id === scope.parentId) { - availableChild.icon = scope.parentIcon; + Utilities.forEach(scope.availableChildren, availableChild => { + if (availableChild.id === scope.parentId) { + availableChild.icon = scope.parentIcon; } }); // update icon on selected child - angular.forEach(scope.selectedChildren, function(selectedChild){ - if(selectedChild.id === scope.parentId) { - selectedChild.icon = scope.parentIcon; + Utilities.forEach(scope.selectedChildren, selectedChild => { + if (selectedChild.id === scope.parentId) { + selectedChild.icon = scope.parentIcon; } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js index fcc02f53a2..bf03749faa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js @@ -91,7 +91,7 @@ } // update selected items - angular.forEach(scope.selectedItems, function (selectedItem) { + Utilities.forEach(scope.selectedItems, selectedItem => { if (selectedItem.placeholder) { selectedItem.name = scope.name; @@ -99,12 +99,11 @@ if (scope.alias !== null && scope.alias !== undefined) { selectedItem.alias = scope.alias; } - } }); // update availableItems - angular.forEach(scope.availableItems, function (availableItem) { + Utilities.forEach(scope.availableItems, availableItem => { if (availableItem.placeholder) { availableItem.name = scope.name; @@ -112,7 +111,6 @@ if (scope.alias !== null && scope.alias !== undefined) { availableItem.alias = scope.alias; } - } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 06e1c61f1e..f09d815ae0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -25,7 +25,7 @@ // set placeholder property on each group if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { addInitProperty(group); }); } @@ -34,14 +34,16 @@ addInitGroup(scope.model.groups); activateFirstGroup(scope.model.groups); + + var labelKeys = [ + "validation_validation", + "contentTypeEditor_tabHasNoSortOrder" + ]; // localize texts - localizationService.localize("validation_validation").then(function (value) { - validationTranslated = value; - }); - - localizationService.localize("contentTypeEditor_tabHasNoSortOrder").then(function (value) { - tabNoSortOrderTranslated = value; + localizationService.localizeMany(labelKeys).then(data => { + validationTranslated = data[0]; + tabNoSortOrderTranslated = data[1]; }); } @@ -129,7 +131,6 @@ // store this tabs sort order as reference for the next prevSortOrder = group.sortOrder; - } }); @@ -179,12 +180,11 @@ function updatePropertiesSortOrder() { - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { if (group.tabState !== "init") { group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties); } }); - } function setupAvailableContentTypesModel(result) { @@ -242,7 +242,6 @@ scope.sortingMode = true; scope.sortingButtonKey = "general_reorderDone"; - } }; @@ -254,20 +253,19 @@ compositeContentTypes: scope.model.compositeContentTypes, view: "views/common/infiniteeditors/compositions/compositions.html", size: "small", - submit: function () { + submit: () => { // make sure that all tabs has an init property if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { addInitProperty(group); }); } // remove overlay editorService.close(); - }, - close: function (oldModel) { + close: oldModel => { // reset composition changes scope.model.groups = oldModel.contentType.groups; @@ -277,7 +275,7 @@ editorService.close(); }, - selectCompositeContentType: function (selectedContentType) { + selectCompositeContentType: selectedContentType => { var deferred = $q.defer(); @@ -291,7 +289,7 @@ //use a different resource lookup depending on the content type type var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getById : mediaTypeResource.getById; - resourceLookup(selectedContentType.id).then(function (composition) { + resourceLookup(selectedContentType.id).then(composition => { //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and // double check here. var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition); @@ -414,7 +412,7 @@ scope.activateGroup = function (selectedGroup) { // set all other groups that are inactive to active - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { // skip init tab if (group.tabState !== "init") { group.tabState = "inActive"; @@ -452,7 +450,7 @@ // check i init tab already exists var addGroup = true; - angular.forEach(groups, function (group) { + Utilities.forEach(groups, group => { if (group.tabState === "init") { addGroup = false; } @@ -653,7 +651,7 @@ }; // check if there already is an init property - angular.forEach(group.properties, function (property) { + Utilities.forEach(group.properties, property => { if (property.propertyState === "init") { addInitPropertyBool = false; } @@ -669,8 +667,8 @@ function updateSameDataTypes(newProperty) { // find each property - angular.forEach(scope.model.groups, function (group) { - angular.forEach(group.properties, function (property) { + Utilities.forEach(scope.model.groups, group => { + Utilities.forEach(group.properties, property => { if (property.dataTypeId === newProperty.dataTypeId) { @@ -681,9 +679,7 @@ property.dataTypeId = newProperty.dataTypeId; property.dataTypeIcon = newProperty.dataTypeIcon; property.dataTypeName = newProperty.dataTypeName; - } - }); }); } @@ -691,8 +687,8 @@ function hasPropertyOfDataTypeId(dataTypeId) { // look at each property - var result = _.filter(scope.model.groups, function (group) { - return _.filter(group.properties, function (property) { + var result = _.filter(scope.model.groups, group => { + return _.filter(group.properties, property => { return (property.dataTypeId === dataTypeId); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index f7b634a710..b6ba1aaedb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -135,11 +135,11 @@ var found = false; scope.listViewAnimation = "out"; - angular.forEach(miniListViewsHistory, function(historyItem, index){ + Utilities.forEach(miniListViewsHistory, (historyItem, index) => { // We need to make sure we can compare the two id's. // Some id's are integers and others are strings. // Members have string ids like "all-members". - if(historyItem.node.id.toString() === ancestor.id.toString()) { + if (historyItem.node.id.toString() === ancestor.id.toString()) { // load the list view from history scope.miniListViews = []; scope.miniListViews.push(historyItem); @@ -149,7 +149,7 @@ } }); - if(!found) { + if (!found) { // if we can't find the view in the history - close the list view scope.exitMiniListView(); } @@ -161,7 +161,7 @@ scope.showBackButton = function() { // don't show the back button if the start node is a list view - if(scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) { + if (scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) { return false; } else { return true; @@ -178,7 +178,7 @@ function makeBreadcrumb() { scope.breadcrumb = []; - angular.forEach(miniListViewsHistory, function(historyItem){ + Utilities.forEach(miniListViewsHistory, historyItem => { scope.breadcrumb.push(historyItem.node); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js index f9b26c81a5..5c8ef162fa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js @@ -15,7 +15,7 @@ var selectedTab = $scope.model.variants[0].tabs[0]; if ($scope.tabAlias) { - angular.forEach($scope.model.variants[0].tabs, function (tab) { + Utilities.forEach($scope.model.variants[0].tabs, tab => { if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) { selectedTab = tab; return; @@ -33,20 +33,19 @@ $scope.$broadcast("formSubmitting", { scope: $scope }); // Sync the values back - angular.forEach($scope.ngModel.variants[0].tabs, function (tab) { + Utilities.forEach($scope.ngModel.variants[0].tabs, tab => { if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) { - var localPropsMap = selectedTab.properties.reduce(function (map, obj) { + var localPropsMap = selectedTab.properties.reduce((map, obj) => { map[obj.alias] = obj; return map; }, {}); - angular.forEach(tab.properties, function (prop) { + Utilities.forEach(tab.properties, prop => { if (localPropsMap.hasOwnProperty(prop.alias)) { prop.value = localPropsMap[prop.alias].value; } }); - } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 79dfee059e..d80b884dab 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -64,8 +64,7 @@ angular.module("umbraco.directives") function _filesQueued(files, event) { //Push into the queue - angular.forEach(files, - function(file) { + Utilities.forEach(files, file => { if (_filterFile(file) === true) { @@ -81,7 +80,7 @@ angular.module("umbraco.directives") if (!scope.working) { // Upload not allowed if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) { - files.map(function(file) { + files.map(file => { file.uploadStatus = "error"; file.serverErrorMessage = "File type is not allowed here"; scope.rejected.push(file); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js index d43282715e..9381940c74 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js @@ -29,7 +29,7 @@ angular.module("umbraco.directives") var childInputs = tabbableService.tabbable(mutation.target); //For each item in childInputs - override or set HTML attribute tabindex="-1" - angular.forEach(childInputs, function (element) { + Utilities.forEach(childInputs, element => { $(element).attr('tabindex', '-1'); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js index 0b743d0f10..3d8653386f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js @@ -62,7 +62,7 @@ angular.module('umbraco.directives') var found = false; // check if any element has focus - angular.forEach(listItems, function (item, index) { + Utilities.forEach(listItems, (item, index) => { if ($(item).is(":focus")) { // if an element already has focus set the // currentIndex so we navigate from that element diff --git a/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js b/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js index 16ced17075..c6aceeb9e0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js @@ -21,7 +21,7 @@ push: function (retryItem) { retryQueue.push(retryItem); // Call all the onItemAdded callbacks - angular.forEach(service.onItemAddedCallbacks, function (cb) { + Utilities.forEach(service.onItemAddedCallbacks, cb => { try { cb(retryItem); } catch (e) { diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index 534b60c120..e497b1ff7f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -88,9 +88,9 @@ function startUpDynamicContentController($q, $timeout, $scope, dashboardResource evts.push(eventsService.on("appState.tour.complete", function (name, completedTour) { $timeout(function(){ - angular.forEach(vm.tours, function (tourGroup) { - angular.forEach(tourGroup, function (tour) { - if(tour.alias === completedTour.alias) { + Utilities.forEach(vm.tours, tourGroup => { + Utilities.forEach(tourGroup, tour => { + if (tour.alias === completedTour.alias) { tour.completed = true; } }); @@ -100,24 +100,24 @@ function startUpDynamicContentController($q, $timeout, $scope, dashboardResource //proxy remote css through the local server assetsService.loadCss(dashboardResource.getRemoteDashboardCssUrl("content"), $scope); - dashboardResource.getRemoteDashboardContent("content").then( - function (data) { - vm.loading = false; + dashboardResource.getRemoteDashboardContent("content").then(data => { - //test if we have received valid data - //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code - if (data && data.sections) { - vm.dashboard = data; - } else { - vm.showDefault = true; - } - }, - function (exception) { - console.error(exception); - vm.loading = false; + vm.loading = false; + + //test if we have received valid data + //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code + if (data && data.sections) { + vm.dashboard = data; + } else { vm.showDefault = true; - }); + } + }, + function (exception) { + console.error(exception); + vm.loading = false; + vm.showDefault = true; + }); onInit(); diff --git a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js index c9dde30c4f..0c9f8f3154 100644 --- a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js +++ b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js @@ -81,7 +81,7 @@ angular.mock.$Browser = function () { self.defer.cancel = function (deferId) { var fnIndex; - angular.forEach(self.deferredFns, function (fn, index) { + Utilities.forEach(self.deferredFns, function (fn, index) { if (fn.id === deferId) fnIndex = index; }); @@ -141,7 +141,7 @@ angular.mock.$Browser.prototype = { * run all fns in pollFns */ poll: function poll() { - angular.forEach(this.pollFns, function (pollFn) { + Utilities.forEach(this.pollFns, function (pollFn) { pollFn(); }); }, @@ -388,9 +388,9 @@ angular.mock.$LogProvider = function () { */ $log.assertEmpty = function () { var errors = []; - angular.forEach(['error', 'warn', 'info', 'log'], function (logLevel) { - angular.forEach($log[logLevel].logs, function (log) { - angular.forEach(log, function (logItem) { + Utilities.forEach(['error', 'warn', 'info', 'log'], function (logLevel) { + Utilities.forEach($log[logLevel].logs, function (log) { + Utilities.forEach(log, function (logItem) { errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); }); }); @@ -598,7 +598,7 @@ angular.mock.$LogProvider = function () { 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - angular.forEach(unimplementedMethods, function (methodName) { + Utilities.forEach(unimplementedMethods, function (methodName) { self[methodName] = function () { throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); }; @@ -688,13 +688,13 @@ angular.mock.dump = function (object) { if (angular.isElement(object)) { object = $(object); out = $('
    '); - angular.forEach(object, function (element) { + Utilities.forEach(object, function (element) { out.append($(element).clone()); }); out = out.html(); } else if (Utilities.isArray(object)) { out = []; - angular.forEach(object, function (o) { + Utilities.forEach(object, function (o) { out.push(serialize(o)); }); out = '[ ' + out.join(', ') + ' ]'; @@ -1343,13 +1343,13 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { function createShortMethods(prefix) { - angular.forEach(['GET', 'DELETE', 'JSONP'], function (method) { + Utilities.forEach(['GET', 'DELETE', 'JSONP'], function (method) { $httpBackend[prefix + method] = function (url, headers) { return $httpBackend[prefix](method, url, undefined, headers) } }); - angular.forEach(['PUT', 'POST', 'PATCH'], function (method) { + Utilities.forEach(['PUT', 'POST', 'PATCH'], function (method) { $httpBackend[prefix + method] = function (url, data, headers) { return $httpBackend[prefix](method, url, data, headers) } @@ -1425,7 +1425,7 @@ function MockXhr() { if (header) return header; header = undefined; - angular.forEach(this.$$respHeaders, function (headerVal, headerName) { + Utilities.forEach(this.$$respHeaders, function (headerVal, headerName) { if (!header && headerName.toLowerCase() == name) header = headerVal; }); return header; @@ -1434,7 +1434,7 @@ function MockXhr() { this.getAllResponseHeaders = function () { var lines = []; - angular.forEach(this.$$respHeaders, function (value, key) { + Utilities.forEach(this.$$respHeaders, function (value, key) { lines.push(key + ': ' + value); }); return lines.join('\n'); @@ -1723,7 +1723,7 @@ window.jstestdriver && (function (window) { */ window.dump = function () { var args = []; - angular.forEach(arguments, function (arg) { + Utilities.forEach(arguments, function (arg) { args.push(angular.mock.dump(arg)); }); jstestdriver.console.log.apply(jstestdriver.console, args); @@ -1757,13 +1757,13 @@ window.jstestdriver && (function (window) { angular.mock.clearDataCache(); // clean up jquery's fragment cache - angular.forEach(angular.element.fragments, function (val, key) { + Utilities.forEach(angular.element.fragments, function (val, key) { delete angular.element.fragments[key]; }); MockXhr.$$lastInstance = null; - angular.forEach(angular.callbacks, function (val, key) { + Utilities.forEach(angular.callbacks, function (val, key) { delete angular.callbacks[key]; }); angular.callbacks.counter = 0; @@ -1798,7 +1798,7 @@ window.jstestdriver && (function (window) { throw Error('Injector already created, can not register a module!'); } else { var modules = currentSpec.$modules || (currentSpec.$modules = []); - angular.forEach(moduleFns, function (module) { + Utilities.forEach(moduleFns, function (module) { modules.push(module); }); } From d9555ea57cfd6e4b8605ca50cd6480a01615d8f8 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 2 Aug 2021 00:31:41 +0200 Subject: [PATCH 25/92] Update noUiSlider to v15.2.0 (#10319) * Upgrade to noUiSlider v15.1.1 * Configurate on drag event * Update nouislider dependencies * Update description * Upgrade to latest noUISlider v15.2.0 --- src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js | 6 +++--- src/Umbraco.Web.UI.Client/package.json | 2 +- .../directives/components/umbrangeslider.directive.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index b5339b60c9..7c4abcf50c 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -207,10 +207,10 @@ function dependencies() { { "name": "nouislider", "src": [ - "./node_modules/nouislider/distribute/nouislider.min.js", - "./node_modules/nouislider/distribute/nouislider.min.css" + "./node_modules/nouislider/dist/nouislider.min.js", + "./node_modules/nouislider/dist/nouislider.min.css" ], - "base": "./node_modules/nouislider/distribute" + "base": "./node_modules/nouislider/dist" }, { "name": "signalr", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 45411fad26..1221728a26 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -41,7 +41,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "14.6.4", + "nouislider": "15.2.0", "npm": "^6.14.7", "signalr": "2.4.0", "spectrum-colorpicker2": "2.0.8", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js index ed74f94f26..d5c791281c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js @@ -52,6 +52,7 @@ For extra details about options and events take a look here: https://refreshless @param {callback} onSlide (callback): onSlide gets triggered when the handle is being dragged. @param {callback} onSet (callback): onSet will trigger every time a slider stops changing. @param {callback} onChange (callback): onChange fires when a user stops sliding, or when a slider value is changed by 'tap'. +@param {callback} onDrag (callback): onDrag fires when a connect element between handles is being dragged, while ignoring other updates to the slider values. @param {callback} onStart (callback): onStart fires when a handle is clicked (mousedown, or the equivalent touch events). @param {callback} onEnd (callback): onEnd fires when a handle is released (mouseup etc), or when a slide is canceled due to other reasons. **/ @@ -71,6 +72,7 @@ For extra details about options and events take a look here: https://refreshless onSlide: '&?', onSet: '&?', onChange: '&?', + onDrag: '&?', onStart: '&?', onEnd: '&?' } @@ -181,6 +183,15 @@ For extra details about options and events take a look here: https://refreshless }); } + // bind hook for drag + if (ctrl.onDrag) { + sliderInstance.noUiSlider.on('drag', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onDrag({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); + }); + }); + } + // bind hook for start if (ctrl.onStart) { sliderInstance.noUiSlider.on('start', function (values, handle, unencoded, tap, positions) { From 050cdd2bb040ed54e8fcb396fa2ba9fd3944baf9 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 2 Aug 2021 00:32:54 +0200 Subject: [PATCH 26/92] Update icon in member group picker (#10094) * Use member group icon as default icon * Make pickers consistent using member group entity icon * Remove unused functions * Use vm * Use original getSelected() function * Include clear function again * Fix merge conflict * Remove comment - remove function is requested from in view * Remove comment - wasn't used, but intend to use in PR 10096 to use property actions to clear member group picker --- .../membergrouppicker.controller.js | 32 +++++++------------ .../membergrouppicker/membergrouppicker.html | 17 +++++----- .../membergroups/membergroups.controller.js | 10 ++++-- .../membergroups/membergroups.html | 8 ++--- .../Models/Mapping/MemberMapDefinition.cs | 1 + 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js index 5362cb1f10..e64633ea31 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js @@ -2,6 +2,12 @@ //with a specified callback, this callback will receive an object with a selection on it function memberGroupPicker($scope, editorService, memberGroupResource){ + var vm = this; + + vm.openMemberGroupPicker = openMemberGroupPicker; + vm.remove = remove; + vm.clear = clear; + function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); return str.replace(rgxtrim, ''); @@ -24,7 +30,7 @@ function memberGroupPicker($scope, editorService, memberGroupResource){ } } - $scope.openMemberGroupPicker = function() { + function openMemberGroupPicker() { var memberGroupPicker = { multiPicker: true, submit: function (model) { @@ -52,31 +58,17 @@ function memberGroupPicker($scope, editorService, memberGroupResource){ } }; editorService.memberGroupPicker(memberGroupPicker); - }; + } - // TODO: I don't believe this is used - $scope.remove = function(index){ + function remove(index) { $scope.renderModel.splice(index, 1); setDirty(); - }; + } - // TODO: I don't believe this is used - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - - if (currIds.indexOf(item) < 0) { - $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' }); - setDirty(); - } - }; - - // TODO: I don't believe this is used - $scope.clear = function() { + function clear() { $scope.renderModel = []; setDirty(); - }; + } function renderModelIds() { return _.map($scope.renderModel, function (i) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html index 5a0788149e..e644fac29d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html @@ -1,17 +1,18 @@ -
    +
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js index 2213841ece..af22f6c800 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js @@ -1,4 +1,10 @@ function memberGroupController($scope, editorService, memberGroupResource) { + + var vm = this; + + vm.pickGroup = pickGroup; + vm.removeGroup = removeGroup; + //set the selected to the keys of the dictionary who's value is true $scope.getSelected = function () { var selected = []; @@ -16,7 +22,7 @@ } } - $scope.pickGroup = function() { + function pickGroup() { editorService.memberGroupPicker({ multiPicker: true, submit: function (model) { @@ -39,7 +45,7 @@ }); } - $scope.removeGroup = function (group) { + function removeGroup(group) { $scope.model.value[group] = false; setDirty(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html index abfa628e90..81b9f9d30b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html @@ -1,15 +1,15 @@ -
    +
    + on-remove="vm.removeGroup(group)"> diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index f86e7eb1fc..dcbe6b7534 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -143,6 +143,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -Icon -Trashed -ParentId -Alias private void Map(IMemberGroup source, MemberGroupDisplay target, MapperContext context) { + target.Icon = Constants.Icons.MemberGroup; target.Id = source.Id; target.Key = source.Key; target.Name = source.Name; From d537545fa9f4c9899afe681778c2080bb6852864 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 20:05:47 +0200 Subject: [PATCH 27/92] Use umb-icon component in user history --- .../src/views/common/overlays/user/user.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index 2de04a0147..e284a10556 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -86,13 +86,13 @@
    -
    From d40e503c824939d69842580e24021438f4d85132 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 2 Aug 2021 01:26:30 +0200 Subject: [PATCH 28/92] V8: Fix JS error when using the hide search function in treepicker (#6931) * Fix JS error when using the hide search function in treepicker * updates with same defensive change from original pr - only explores child.children after confirming the child indeed has children Co-authored-by: Nathan Woulfe --- .../common/infiniteeditors/treepicker/treepicker.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 0c5fe9af1b..ed5c4096bc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -574,6 +574,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", var listViewResults = vm.searchInfo.selectedSearchResults.filter(i => i.parentId === child.id); listViewResults.forEach(item => { + if (!child.children) return; + var childExists = child.children.find(c => c.id === item.id); if (!childExists) { From d11b79421d4bbc55e135952e91aed1053b7abaa6 Mon Sep 17 00:00:00 2001 From: Zeegaan <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 4 Aug 2021 09:46:01 +0200 Subject: [PATCH 29/92] fixed a bug where validation didnt update properly adressed in https://github.com/umbraco/Umbraco-CMS/pull/10688 --- .../Controllers/MemberController.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 6cada09db3..d8ee3126a4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -368,7 +368,45 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (created.Succeeded == false) { - return ValidationProblem(created.Errors.ToErrorMessage()); + MemberDisplay forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); + foreach (IdentityError error in created.Errors) + { + switch (error.Code) + { + case nameof(IdentityErrorDescriber.InvalidUserName): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.PasswordMismatch): + case nameof(IdentityErrorDescriber.PasswordRequiresDigit): + case nameof(IdentityErrorDescriber.PasswordRequiresLower): + case nameof(IdentityErrorDescriber.PasswordRequiresNonAlphanumeric): + case nameof(IdentityErrorDescriber.PasswordRequiresUniqueChars): + case nameof(IdentityErrorDescriber.PasswordRequiresUpper): + case nameof(IdentityErrorDescriber.PasswordTooShort): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.InvalidEmail): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.DuplicateUserName): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.DuplicateEmail): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + } + } + return ValidationProblem(forDisplay, ModelState); } // now re-look up the member, which will now exist From 96d593593381c48f57b0201a686957c1a37bddbb Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 4 Aug 2021 14:19:34 +0200 Subject: [PATCH 30/92] Revert "Accessibility improvements for Extensions in Razor Markup (#9576)" This reverts commit a1e0af6fff971af3270b6292b0be38a6988d3a70. --- .../Models/PublishedContent/Fallback.cs | 6 -- .../PublishedContentLanguageVariantTests.cs | 8 +-- .../PublishedValueFallback.cs | 65 +++---------------- 3 files changed, 9 insertions(+), 70 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs index 805f14d21b..0434218555 100644 --- a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs @@ -71,11 +71,5 @@ namespace Umbraco.Core.Models.PublishedContent { return GetEnumerator(); } - - public const int DisplayFallbackLanguage = 4; - /// - /// Gets the fallback to tree ancestors policy. - /// - public static Fallback ToDisplayFallbackLanguage => new Fallback(new[] { DisplayFallbackLanguage }); } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 05cf8d01c1..636f8502ed 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -235,13 +235,7 @@ namespace Umbraco.Tests.PublishedContent var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); Assert.AreEqual("Welcome", value); } - [Test] - public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_And_Language_Change() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.DisplayFallbackLanguage)); - Assert.AreEqual("Welcome", value); - } + [Test] public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() { diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index b5901fef69..57c3094eaf 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web; -using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -37,8 +35,6 @@ namespace Umbraco.Web.Models.PublishedContent { _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); - var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); - foreach (var f in fallback) { switch (f) @@ -49,11 +45,9 @@ namespace Umbraco.Web.Models.PublishedContent value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(property, culture, segment, out value, includeFallbackLanguage)) + if (TryGetValueWithLanguageFallback(property, culture, segment, out value)) return true; break; - case Fallback.DisplayFallbackLanguage: - continue; default: throw NotSupportedFallbackMethod(f, "property"); } @@ -73,7 +67,6 @@ namespace Umbraco.Web.Models.PublishedContent public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) { var propertyType = content.ContentType.GetPropertyType(alias); - var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); if (propertyType == null) { value = default; @@ -92,11 +85,9 @@ namespace Umbraco.Web.Models.PublishedContent value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) return true; break; - case Fallback.DisplayFallbackLanguage: - continue; default: throw NotSupportedFallbackMethod(f, "element"); } @@ -116,7 +107,6 @@ namespace Umbraco.Web.Models.PublishedContent public virtual bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty) { noValueProperty = default; - var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) @@ -141,15 +131,13 @@ namespace Umbraco.Web.Models.PublishedContent case Fallback.Language: if (propertyType == null) continue; - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) return true; break; case Fallback.Ancestors: - if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty, includeFallbackLanguage)) + if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty)) return true; break; - case Fallback.DisplayFallbackLanguage: - continue; default: throw NotSupportedFallbackMethod(f, "content"); } @@ -167,10 +155,9 @@ namespace Umbraco.Web.Models.PublishedContent // tries to get a value, recursing the tree // because we recurse, content may not even have the a property with the specified alias (but only some ancestor) // in case no value was found, noValueProperty contains the first property that was found (which does not have a value) - private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty, bool includeFallbackLanguage) + private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty) { IPublishedProperty property; // if we are here, content's property has no value - var originalCulture = culture; do { content = content.Parent; @@ -196,10 +183,6 @@ namespace Umbraco.Web.Models.PublishedContent if (property != null && property.HasValue(culture, segment)) { value = property.Value(culture, segment); - if (includeFallbackLanguage && originalCulture != culture) - { - value = GetMarkUpForFallbackLanguage(culture, value); - } return true; } @@ -208,7 +191,7 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value, bool includeFallbackLanguage) + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value) { value = default; @@ -234,10 +217,6 @@ namespace Umbraco.Web.Models.PublishedContent if (property.HasValue(culture2, segment)) { value = property.Value(culture2, segment); - if (includeFallbackLanguage && culture2 != culture) - { - value = GetMarkUpForFallbackLanguage(culture2, value); - } return true; } @@ -246,7 +225,7 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value) { value = default; @@ -272,10 +251,6 @@ namespace Umbraco.Web.Models.PublishedContent if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); - if (includeFallbackLanguage && culture2 != culture) - { - value = GetMarkUpForFallbackLanguage(culture2, value); - } return true; } @@ -284,7 +259,7 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value) { value = default; @@ -313,35 +288,11 @@ namespace Umbraco.Web.Models.PublishedContent if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); - if (includeFallbackLanguage && culture2 != culture) - { - value = GetMarkUpForFallbackLanguage(culture2, value); - - } return true; } language = language2; } } - - private T GetMarkUpForFallbackLanguage(string culture2, T value) - { - var typeOfT = typeof(T); - if (value is string) - { - var newValue = "" + value + ""; - return (T)Convert.ChangeType(newValue, typeof(T)); - } - else if (typeOfT == typeof(IHtmlString)) - { - // we want to return a block element here since the IHtmlString could contain futher block elements - var newValue = "
    " + value + "
    "; - IHtmlString htmlString = new MvcHtmlString(newValue); - return (T)htmlString; - } - - return value; - } } } From 14fc72ff2bd85538cc36bab7f013d3738a97d06f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Aug 2021 09:50:58 +0200 Subject: [PATCH 31/92] Reverting ConcurrentHashSet for now so that Umbraco can install again, see #10751 --- .../Collections/ConcurrentHashSet.cs | 109 +++++++++++++++--- src/umbraco.sln.DotSettings | 1 + 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 0424c4dae1..c4dba51acd 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,8 +1,8 @@ using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Umbraco.Core.Collections { @@ -14,10 +14,9 @@ namespace Umbraco.Core.Collections [Serializable] public class ConcurrentHashSet : ICollection { - // Internally we just use a ConcurrentDictionary with the same value for the Value (since we don't actually care about the value) - private static readonly byte _emptyValue = 0x0; - private readonly ConcurrentDictionary _innerSet = new ConcurrentDictionary(); - + private readonly HashSet _innerSet = new HashSet(); + private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + /// /// Returns an enumerator that iterates through the collection. /// @@ -27,7 +26,7 @@ namespace Umbraco.Core.Collections /// 1 public IEnumerator GetEnumerator() { - return _innerSet.Keys.GetEnumerator(); + return GetThreadSafeClone().GetEnumerator(); } /// @@ -51,7 +50,16 @@ namespace Umbraco.Core.Collections /// The object to remove from the .The is read-only. public bool Remove(T item) { - return _innerSet.TryRemove(item, out _); + try + { + _instanceLocker.EnterWriteLock(); + return _innerSet.Remove(item); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } @@ -66,7 +74,17 @@ namespace Umbraco.Core.Collections { get { - return _innerSet.Count; + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Count; + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + } } @@ -81,10 +99,19 @@ namespace Umbraco.Core.Collections /// /// Adds an item to the . /// - /// The object to add to the . + /// The object to add to the .The is read-only. public void Add(T item) { - _innerSet.TryAdd(item, _emptyValue); + try + { + _instanceLocker.EnterWriteLock(); + _innerSet.Add(item); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -94,7 +121,21 @@ namespace Umbraco.Core.Collections /// public bool TryAdd(T item) { - return _innerSet.TryAdd(item, _emptyValue); + if (Contains(item)) return false; + try + { + _instanceLocker.EnterWriteLock(); + + //double check + if (_innerSet.Contains(item)) return false; + _innerSet.Add(item); + return true; + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -103,7 +144,16 @@ namespace Umbraco.Core.Collections /// The is read-only. public void Clear() { - _innerSet.Clear(); + try + { + _instanceLocker.EnterWriteLock(); + _innerSet.Clear(); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -115,7 +165,16 @@ namespace Umbraco.Core.Collections /// The object to locate in the . public bool Contains(T item) { - return _innerSet.ContainsKey(item); + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Contains(item); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } } /// @@ -124,8 +183,24 @@ namespace Umbraco.Core.Collections /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(T[] array, int index) { - var keys = _innerSet.Keys; - keys.CopyTo(array, index); + var clone = GetThreadSafeClone(); + clone.CopyTo(array, index); + } + + private HashSet GetThreadSafeClone() + { + HashSet clone = null; + try + { + _instanceLocker.EnterReadLock(); + clone = new HashSet(_innerSet, _innerSet.Comparer); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + return clone; } /// @@ -134,8 +209,8 @@ namespace Umbraco.Core.Collections /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 public void CopyTo(Array array, int index) { - var keys = _innerSet.Keys; - Array.Copy(keys.ToArray(), 0, array, index, keys.Count); + var clone = GetThreadSafeClone(); + Array.Copy(clone.ToArray(), 0, array, index, clone.Count); } } } diff --git a/src/umbraco.sln.DotSettings b/src/umbraco.sln.DotSettings index 3d42d188af..bd8ca8b974 100644 --- a/src/umbraco.sln.DotSettings +++ b/src/umbraco.sln.DotSettings @@ -1,4 +1,5 @@  + True <data><IncludeFilters /><ExcludeFilters /></data> <data /> Disposable construction From 6a12f4af9033d3ab58e9aa1102449045ba511c7f Mon Sep 17 00:00:00 2001 From: Rowan Bottema Date: Thu, 5 Aug 2021 11:10:58 +0200 Subject: [PATCH 32/92] Update Serilog dependencies This should at least fix #10793 because of a memory leak fix in Serilog.Sinks.Map 1.0.2. Also updated the rest for good measure. Note that Serilog.Sinks.File is not upgraded to the latest version because it is a new major (4.1.0 -> 5.0.0). Also note that Serilog.Filters.Expressions is deprecated and should be replaced by Serilog.Expressions. I considered both out of scope for this fix. --- build/NuSpecs/UmbracoCms.Core.nuspec | 16 ++++++++-------- src/Umbraco.Core/Umbraco.Core.csproj | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index fce15eb487..c94def143b 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -32,16 +32,16 @@ - + - - - - + + + + - - - + + + diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5dfcd14469..146197c4f8 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -67,10 +67,10 @@ all - 1.3.0 + 1.5.0 - 1.0.0 + 1.0.2 1.0.5 @@ -94,28 +94,28 @@ - 2.8.0 + 2.10.0 2.0.1 - 3.0.0 + 3.1.0 - 2.0.0 + 2.1.0 - 1.0.0 + 1.1.0 - 1.0.3 + 1.0.5 2.2.2 - 4.0.0 + 4.1.0 From 7800fe83a8978440be9debc6800d1403f8fdfc0b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Aug 2021 11:57:03 +0200 Subject: [PATCH 33/92] Install MessagePack's dependencies explicitly (#10772) --- build/NuSpecs/UmbracoCms.Web.nuspec | 4 ++++ build/NuSpecs/tools/Web.config.install.xdt | 17 ++++++++++++++++- src/Umbraco.Tests/App.config | 14 +++++++++++++- src/Umbraco.Web.UI/web.Template.config | 15 +++++++++++++-- src/Umbraco.Web/Umbraco.Web.csproj | 12 ++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 7aebfae108..04b9905f9d 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -41,7 +41,11 @@ + + + + diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index e215bdbf29..fe5b7db704 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -131,6 +131,9 @@ + + + @@ -153,7 +156,7 @@ - + @@ -171,6 +174,18 @@ + + + + + + + + + + + + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 4af04827e3..c8e8fdac11 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -76,7 +76,7 @@ - + @@ -98,6 +98,18 @@ + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index c6b1eb686c..e61c6585ad 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -236,7 +236,7 @@ - + @@ -258,7 +258,18 @@ - + + + + + + + + + + + + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e830722cc7..316f5def8e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -101,6 +101,18 @@ all + + 4.5.1 + + + 4.5.4 + + + 4.5.0 + + + 1.7.1 + 1.0.5 From a7c0edec288c62ce996fd85226831a39e88df33d Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 5 Aug 2021 15:05:03 +0200 Subject: [PATCH 34/92] Support custom SVG icons in user groups list (#10739) --- .../infiniteeditors/usergrouppicker/usergrouppicker.html | 4 ++-- .../views/components/users/umb-user-group-preview.html | 8 ++++---- .../src/views/users/views/groups/groups.html | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html index 252c1b7cd5..dbddb141dd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html @@ -18,7 +18,7 @@
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html index 20718cf804..db0c6c86c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html @@ -1,7 +1,7 @@
    - - - + + +
    {{ name }}
    @@ -20,7 +20,7 @@ {{ contentStartNode.name }}
    - +
    Media start node: diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html index 46923dd27d..106a8c8454 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html @@ -81,8 +81,8 @@ ng-class="{'-selected': group.selected, '-selectable': group.hasAccess && !group.isSystemUserGroup}">
    - - + +
    Date: Thu, 5 Aug 2021 15:22:23 +0200 Subject: [PATCH 35/92] Tour backdrop missing (#10762) --- .../components/tree/umbtree.directive.js | 16 ++-------------- .../src/common/services/backdrop.service.js | 14 +++++++------- .../src/common/services/navigation.service.js | 18 ++++++++++++------ .../src/common/services/overlay.service.js | 7 ++++++- .../components/application/umb-backdrop.html | 2 +- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 7868f79809..4512494202 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -3,7 +3,7 @@ * @name umbraco.directives.directive:umbTree * @restrict E **/ -function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService, backdropService) { +function umbTreeDirective($q, treeService, navigationService, notificationsService) { return { restrict: 'E', @@ -318,18 +318,6 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } - // Close any potential backdrop and remove the #leftcolumn modifier class - function closeBackdrop() { - var aboveClass = 'above-backdrop'; - var leftColumn = $('#leftcolumn'); - var isLeftColumnOnTop = leftColumn.hasClass(aboveClass); - - if(isLeftColumnOnTop){ - backdropService.close(); - leftColumn.removeClass(aboveClass); - } - } - /** Returns the css classses assigned to the node (div element) */ $scope.getNodeCssClass = function (node) { if (!node) { @@ -370,7 +358,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use */ $scope.select = function (n, ev) { - closeBackdrop() + navigationService.closeBackdrop(); if (n.metaData && n.metaData.noAccess === true) { ev.preventDefault(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js b/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js index 4f977cb1b2..e628e48306 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js @@ -35,11 +35,11 @@ */ function open(options) { - if(options && options.element) { + if (options && options.element) { args.element = options.element; } - if(options && options.disableEventsOnClick) { + if (options && options.disableEventsOnClick) { args.disableEventsOnClick = options.disableEventsOnClick; } @@ -58,11 +58,11 @@ * */ function close() { - args.opacity = null, - args.element = null, - args.elementPreventClick = false, - args.disableEventsOnClick = false, - args.show = false + args.opacity = null; + args.element = null; + args.elementPreventClick = false; + args.disableEventsOnClick = false; + args.show = false; eventsService.emit("appState.backdrop", args); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index c628e3a5b1..bba44a94a0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -118,19 +118,25 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } function closeBackdrop() { - var aboveClass = 'above-backdrop'; - var leftColumn = $('#leftcolumn'); - var isLeftColumnOnTop = leftColumn.hasClass(aboveClass); - if(isLeftColumnOnTop){ + var tourIsOpen = document.body.classList.contains("umb-tour-is-visible"); + if (tourIsOpen) { + return; + } + + var aboveClass = "above-backdrop"; + var leftColumn = document.getElementById("leftcolumn"); + var isLeftColumnOnTop = leftColumn.classList.contains(aboveClass); + + if (isLeftColumnOnTop) { backdropService.close(); - leftColumn.removeClass(aboveClass); + leftColumn.classList.remove(aboveClass); } } function showBackdrop() { var backDropOptions = { - 'element': $('#leftcolumn')[0] + 'element': document.getElementById('leftcolumn') }; backdropService.open(backDropOptions); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index ea05dad4e7..8a965f2c78 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -51,7 +51,12 @@ function close() { focusLockService.removeInertAttribute(); - backdropService.close(); + + var tourIsOpen = document.body.classList.contains("umb-tour-is-visible"); + if (!tourIsOpen) { + backdropService.close(); + } + currentOverlay = null; eventsService.emit("appState.overlay", null); } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html index da1f61ee4a..1ff3960ae9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html @@ -10,7 +10,7 @@
    -
    +
    From 4513a85d14d7fda505f8c792d4082ff764314af9 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 20:31:31 +0200 Subject: [PATCH 36/92] Only set umb-button-ellipsis opacity to 1 when above backdrop --- .../less/components/buttons/umb-button-ellipsis.less | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less index 7104e6478f..b6acb2f6cf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less @@ -1,4 +1,4 @@ -.umb-button-ellipsis{ +.umb-button-ellipsis { padding: 0 5px; text-align: center; margin: 0 auto; @@ -28,13 +28,13 @@ } .umb-button-ellipsis--tab, - .umb-tour-is-visible .umb-tree &, + .umb-tour-is-visible .umb-tree .umb-tree-item.above-backdrop &, &:hover, - &:focus{ + &:focus { opacity: 1; } - &--hidden{ + &--hidden { opacity: 0; &:hover, @@ -55,7 +55,7 @@ .umb-button-ellipsis--tab & { margin: 0 0 7px; - } + } .umb-button-ellipsis--small & { font-size: 8px; From 120bc2af115f904dc07b74fa00fd61ca46218832 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 5 Aug 2021 15:57:14 +0200 Subject: [PATCH 37/92] Examine dashboard search adjustments (#10735) --- .../forms/umbsearchfilter.directive.js | 21 +++++++ .../components/application/umb-search.less | 2 +- .../src/less/utilities/_spacing.less | 8 +++ .../blockpicker/blockpicker.html | 2 +- .../components/forms/umb-search-filter.html | 2 + .../settings/examinemanagement.controller.js | 2 +- .../dashboard/settings/examinemanagement.html | 55 ++++++++++++------- 7 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js index 2e9f15913c..5eb22dcecd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js @@ -40,6 +40,8 @@ vm.$onInit = onInit; vm.change = change; + vm.keyDown = keyDown; + vm.blur = blur; function onInit() { vm.inputId = vm.inputId || "umb-search-filter_" + String.CreateGuid(); @@ -63,6 +65,23 @@ }, 0); } } + + function blur() { + if (vm.onBlur) { + vm.onBlur(); + } + } + + function keyDown(evt) { + //13: enter + switch (evt.keyCode) { + case 13: + if (vm.onSearch) { + vm.onSearch(); + } + break; + } + } } var component = { @@ -76,6 +95,8 @@ text: "@", labelKey: "@?", onChange: "&?", + onSearch: "&?", + onBlur: "&?", autoFocus: "
    - + +
    @@ -59,7 +61,7 @@
    - +
    @@ -116,15 +118,19 @@
    -