Merge remote-tracking branch 'origin/v8/dev' into netcore/feature/merge-v8-18-01-2021

# Conflicts:
#	.gitignore
#	build/NuSpecs/UmbracoCms.Core.nuspec
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoSettings/BackOfficeElement.cs
#	src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
#	src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs
#	src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
#	src/Umbraco.Core/IO/SystemFiles.cs
#	src/Umbraco.Core/Models/ContentBase.cs
#	src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
#	src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs
#	src/Umbraco.Core/Runtime/CoreRuntime.cs
#	src/Umbraco.Core/RuntimeOptions.cs
#	src/Umbraco.Core/RuntimeState.cs
#	src/Umbraco.Core/Telemetry/TelemetryMarkerComponent.cs
#	src/Umbraco.Core/Telemetry/TelemetryMarkerComposer.cs
#	src/Umbraco.Examine/Umbraco.Examine.csproj
#	src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs
#	src/Umbraco.Infrastructure/Install/InstallStepCollection.cs
#	src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs
#	src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
#	src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
#	src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
#	src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs
#	src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
#	src/Umbraco.Tests/Runtimes/StandaloneTests.cs
#	src/Umbraco.Tests/Testing/TestDatabase.cs
#	src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
#	src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js
#	src/Umbraco.Web.UI.NetCore/Views/Partials/Grid/Editors/TextString.cshtml
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml
#	src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml
#	src/Umbraco.Web.UI/config/umbracoSettings.Release.config
#	src/Umbraco.Web/Composing/CompositionExtensions/Installer.cs
#	src/Umbraco.Web/Editors/PreviewController.cs
#	src/Umbraco.Web/Editors/UsersController.cs
#	src/Umbraco.Web/JavaScript/PreviewInitialize.js
#	src/Umbraco.Web/Telemetry/TelemetryComponent.cs
#	src/Umbraco.Web/UmbracoApplication.cs
This commit is contained in:
Bjarke Berg
2021-01-18 15:40:22 +01:00
165 changed files with 5554 additions and 2998 deletions

View File

@@ -98,10 +98,15 @@ namespace Umbraco.Core.Models
set
{
if (_schedule != null)
_schedule.CollectionChanged -= ScheduleCollectionChanged;
{
_schedule.ClearCollectionChangedEvents();
}
SetPropertyValueAndDetectChanges(value, ref _schedule, nameof(ContentSchedule));
if (_schedule != null)
{
_schedule.CollectionChanged += ScheduleCollectionChanged;
}
}
}
@@ -223,10 +228,16 @@ namespace Umbraco.Core.Models
}
set
{
if (_publishInfos != null) _publishInfos.CollectionChanged -= PublishNamesCollectionChanged;
if (_publishInfos != null)
{
_publishInfos.ClearCollectionChangedEvents();
}
_publishInfos = value;
if (_publishInfos != null)
{
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
}
}
}
@@ -321,7 +332,7 @@ namespace Umbraco.Core.Models
else
Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes);
Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add
Properties.ClearCollectionChangedEvents(); // be sure not to double add
Properties.CollectionChanged += PropertiesChanged;
}
@@ -438,7 +449,7 @@ namespace Umbraco.Core.Models
//if culture infos exist then deal with event bindings
if (clonedContent._publishInfos != null)
{
clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any
clonedContent._publishInfos.ClearCollectionChangedEvents(); //clear this event handler if any
clonedContent._publishInfos = (ContentCultureInfosCollection)_publishInfos.DeepClone(); //manually deep clone
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler
}
@@ -446,7 +457,7 @@ namespace Umbraco.Core.Models
//if properties exist then deal with event bindings
if (clonedContent._schedule != null)
{
clonedContent._schedule.CollectionChanged -= ScheduleCollectionChanged; //clear this event handler if any
clonedContent._schedule.ClearCollectionChangedEvents(); //clear this event handler if any
clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone
clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler
}

View File

@@ -138,7 +138,11 @@ namespace Umbraco.Core.Models
get => _properties;
set
{
if (_properties != null) _properties.CollectionChanged -= PropertiesChanged;
if (_properties != null)
{
_properties.ClearCollectionChangedEvents();
}
_properties = value;
_properties.CollectionChanged += PropertiesChanged;
}
@@ -173,10 +177,15 @@ namespace Umbraco.Core.Models
}
set
{
if (_cultureInfos != null) _cultureInfos.CollectionChanged -= CultureInfosCollectionChanged;
if (_cultureInfos != null)
{
_cultureInfos.ClearCollectionChangedEvents();
}
_cultureInfos = value;
if (_cultureInfos != null)
{
_cultureInfos.CollectionChanged += CultureInfosCollectionChanged;
}
}
}
@@ -479,7 +488,7 @@ namespace Umbraco.Core.Models
//if culture infos exist then deal with event bindings
if (clonedContent._cultureInfos != null)
{
clonedContent._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any
clonedContent._cultureInfos.ClearCollectionChangedEvents(); //clear this event handler if any
clonedContent._cultureInfos = (ContentCultureInfosCollection)_cultureInfos.DeepClone(); //manually deep clone
clonedContent._cultureInfos.CollectionChanged += clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler
}
@@ -487,7 +496,7 @@ namespace Umbraco.Core.Models
//if properties exist then deal with event bindings
if (clonedContent._properties != null)
{
clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any
clonedContent._properties.ClearCollectionChangedEvents(); //clear this event handler if any
clonedContent._properties = (IPropertyCollection)_properties.DeepClone(); //manually deep clone
clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler
}

View File

@@ -14,6 +14,11 @@ namespace Umbraco.Core.Models
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Clears all <see cref="CollectionChanged"/> event handlers
/// </summary>
public void ClearCollectionChangedEvents() => CollectionChanged = null;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);

View File

@@ -266,7 +266,10 @@ namespace Umbraco.Core.Models
set
{
if (_noGroupPropertyTypes != null)
_noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged;
{
_noGroupPropertyTypes.ClearCollectionChangedEvents();
}
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing, value);
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
@@ -484,14 +487,14 @@ namespace Umbraco.Core.Models
// its ignored from the auto-clone process because its return values are unions, not raw and
// we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842
clonedEntity._noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any
clonedEntity._noGroupPropertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any
clonedEntity._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone
clonedEntity._noGroupPropertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler
}
if (clonedEntity._propertyGroups != null)
{
clonedEntity._propertyGroups.CollectionChanged -= PropertyGroupsChanged; //clear this event handler if any
clonedEntity._propertyGroups.ClearCollectionChangedEvents(); //clear this event handler if any
clonedEntity._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone
clonedEntity._propertyGroups.CollectionChanged += clonedEntity.PropertyGroupsChanged; //re-assign correct event handler
}

View File

@@ -34,5 +34,6 @@ namespace Umbraco.Core.Models
void Add(IProperty property);
int Count { get; }
void ClearCollectionChangedEvents();
}
}

View File

@@ -77,7 +77,7 @@ namespace Umbraco.Core.Models
else
Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes);
Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add
Properties.ClearCollectionChangedEvents(); // be sure not to double add
Properties.CollectionChanged += PropertiesChanged;
}
}

View File

@@ -155,6 +155,8 @@ namespace Umbraco.Core.Models
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void ClearCollectionChangedEvents() => CollectionChanged = null;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);

View File

@@ -66,7 +66,10 @@ namespace Umbraco.Core.Models
set
{
if (_propertyTypes != null)
_propertyTypes.CollectionChanged -= PropertyTypesChanged;
{
_propertyTypes.ClearCollectionChangedEvents();
}
_propertyTypes = value;
// since we're adding this collection to this group,
@@ -100,7 +103,7 @@ namespace Umbraco.Core.Models
if (clonedEntity._propertyTypes != null)
{
clonedEntity._propertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any
clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any
clonedEntity._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone
clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler
}

View File

@@ -160,6 +160,11 @@ namespace Umbraco.Core.Models
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Clears all <see cref="CollectionChanged"/> event handlers
/// </summary>
public void ClearCollectionChangedEvents() => CollectionChanged = null;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);

View File

@@ -165,7 +165,11 @@ namespace Umbraco.Core.Models
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Clears all <see cref="CollectionChanged"/> event handlers
/// </summary>
public void ClearCollectionChangedEvents() => CollectionChanged = null;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);

View File

@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core.Collections;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
@@ -12,7 +13,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class PublicAccessEntry : EntityBase
{
private readonly ObservableCollection<PublicAccessRule> _ruleCollection;
private readonly EventClearingObservableCollection<PublicAccessRule> _ruleCollection;
private int _protectedNodeId;
private int _noAccessNodeId;
private int _loginNodeId;
@@ -28,7 +29,7 @@ namespace Umbraco.Core.Models
NoAccessNodeId = noAccessNode.Id;
_protectedNodeId = protectedNode.Id;
_ruleCollection = new ObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection = new EventClearingObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged;
foreach (var rule in _ruleCollection)
@@ -44,7 +45,7 @@ namespace Umbraco.Core.Models
NoAccessNodeId = noAccessNodeId;
_protectedNodeId = protectedNodeId;
_ruleCollection = new ObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection = new EventClearingObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged;
foreach (var rule in _ruleCollection)
@@ -148,7 +149,7 @@ namespace Umbraco.Core.Models
if (cloneEntity._ruleCollection != null)
{
cloneEntity._ruleCollection.CollectionChanged -= _ruleCollection_CollectionChanged; //clear this event handler if any
cloneEntity._ruleCollection.ClearCollectionChangedEvents(); //clear this event handler if any
cloneEntity._ruleCollection.CollectionChanged += cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler
}
}

View File

@@ -1,52 +1,130 @@
using System;
using System.Globalization;
namespace Umbraco.Core.Models
{
// <summary>The Range class. Adapted from http://stackoverflow.com/questions/5343006/is-there-a-c-sharp-type-for-representing-an-integer-range</summary>
/// <typeparam name="T">Generic parameter.</typeparam>
public class Range<T> where T : IComparable<T>
/// <summary>
/// Represents a range with a minimum and maximum value.
/// </summary>
/// <typeparam name="T">The type of the minimum and maximum values.</typeparam>
/// <seealso cref="System.IEquatable{Umbraco.Core.Models.Range{T}}" />
public class Range<T> : IEquatable<Range<T>>
where T : IComparable<T>
{
/// <summary>Minimum value of the range.</summary>
/// <summary>
/// Gets or sets the minimum value.
/// </summary>
/// <value>
/// The minimum value.
/// </value>
public T Minimum { get; set; }
/// <summary>Maximum value of the range.</summary>
/// <summary>
/// Gets or sets the maximum value.
/// </summary>
/// <value>
/// The maximum value.
/// </value>
public T Maximum { get; set; }
/// <summary>Presents the Range in readable format.</summary>
/// <returns>String representation of the Range</returns>
public override string ToString()
{
return string.Format("{0},{1}", this.Minimum, this.Maximum);
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString() => this.ToString("{0},{1}", CultureInfo.InvariantCulture);
/// <summary>Determines if the range is valid.</summary>
/// <returns>True if range is valid, else false</returns>
public bool IsValid()
{
return this.Minimum.CompareTo(this.Maximum) <= 0;
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <param name="format">A composite format string for a single value (minimum and maximum are equal). Use {0} for the minimum and {1} for the maximum value.</param>
/// <param name="formatRange">A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public string ToString(string format, string formatRange, IFormatProvider provider = null) => this.ToString(this.Minimum.CompareTo(this.Maximum) == 0 ? format : formatRange, provider);
/// <summary>Determines if the provided value is inside the range.</summary>
/// <param name="value">The value to test</param>
/// <returns>True if the value is inside Range, else false</returns>
public bool ContainsValue(T value)
{
return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0);
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <param name="format">A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public string ToString(string format, IFormatProvider provider = null) => string.Format(provider, format, this.Minimum, this.Maximum);
/// <summary>Determines if this Range is inside the bounds of another range.</summary>
/// <param name="Range">The parent range to test on</param>
/// <returns>True if range is inclusive, else false</returns>
public bool IsInsideRange(Range<T> range)
{
return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
}
/// <summary>
/// Determines whether this range is valid (the minimum value is lower than or equal to the maximum value).
/// </summary>
/// <returns>
/// <c>true</c> if this range is valid; otherwise, <c>false</c>.
/// </returns>
public bool IsValid() => this.Minimum.CompareTo(this.Maximum) <= 0;
/// <summary>Determines if another range is inside the bounds of this range.</summary>
/// <param name="Range">The child range to test</param>
/// <returns>True if range is inside, else false</returns>
public bool ContainsRange(Range<T> range)
{
return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
}
/// <summary>
/// Determines whether this range contains the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>
/// <c>true</c> if this range contains the specified value; otherwise, <c>false</c>.
/// </returns>
public bool ContainsValue(T value) => this.Minimum.CompareTo(value) <= 0 && value.CompareTo(this.Maximum) <= 0;
/// <summary>
/// Determines whether this range is inside the specified range.
/// </summary>
/// <param name="range">The range.</param>
/// <returns>
/// <c>true</c> if this range is inside the specified range; otherwise, <c>false</c>.
/// </returns>
public bool IsInsideRange(Range<T> range) => this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
/// <summary>
/// Determines whether this range contains the specified range.
/// </summary>
/// <param name="range">The range.</param>
/// <returns>
/// <c>true</c> if this range contains the specified range; otherwise, <c>false</c>.
/// </returns>
public bool ContainsRange(Range<T> range) => this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
/// <summary>
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj) => obj is Range<T> other && this.Equals(other);
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <see langword="true" /> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <see langword="false" />.
/// </returns>
public bool Equals(Range<T> other) => other != null && this.Equals(other.Minimum, other.Maximum);
/// <summary>
/// Determines whether the specified <paramref name="minimum" /> and <paramref name="maximum" /> values are equal to this instance values.
/// </summary>
/// <param name="minimum">The minimum value.</param>
/// <param name="maximum">The maximum value.</param>
/// <returns>
/// <c>true</c> if the specified <paramref name="minimum" /> and <paramref name="maximum" /> values are equal to this instance values; otherwise, <c>false</c>.
/// </returns>
public bool Equals(T minimum, T maximum) => this.Minimum.CompareTo(minimum) == 0 && this.Maximum.CompareTo(maximum) == 0;
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode() => (this.Minimum, this.Maximum).GetHashCode();
}
}