diff --git a/.github/BUILD.md b/.github/BUILD.md
index ad33872423..5f962a8911 100644
--- a/.github/BUILD.md
+++ b/.github/BUILD.md
@@ -39,7 +39,7 @@ To build Umbraco, fire up PowerShell and move to Umbraco's repository root (the
build/build.ps1
-If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (dev-v8) the file will appear and you can build it.
+If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (v8/contrib) the file will appear and you can build it.
You might run into [Powershell quirks](#powershell-quirks).
diff --git a/.gitignore b/.gitignore
index ea2ddfbb68..15c9fb52f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -198,3 +198,5 @@ src/Umbraco.Tests.Integration/Views/
src/Umbraco.Tests/TEMP/
/src/Umbraco.Web.UI.NetCore/Umbraco/Data/*
+
+/src/Umbraco.Web.UI/config/umbracoSettings.config
diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt
index 91d49d896c..fd1b2174e2 100644
--- a/build/NuSpecs/tools/ReadmeUpgrade.txt
+++ b/build/NuSpecs/tools/ReadmeUpgrade.txt
@@ -11,7 +11,7 @@ Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
Don't forget to build!
-We've done our best to transform your configuration files but in case something is not quite right: we recommmend you look in source control for the previous version so you can find the original files before they were transformed.
+We've done our best to transform your configuration files but in case something is not quite right: we recommend you look in source control for the previous version so you can find the original files before they were transformed.
This NuGet package includes build targets that extend the creation of a deploy package, which is generated by
Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 0a2e2c5c54..3a1f151388 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
-[assembly: AssemblyCopyright("Copyright © Umbraco 2020")]
+[assembly: AssemblyCopyright("Copyright © Umbraco 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs
new file mode 100644
index 0000000000..af25cc1b4a
--- /dev/null
+++ b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+namespace Umbraco.Core.Collections
+{
+ ///
+ /// Allows clearing all event handlers
+ ///
+ ///
+ public class EventClearingObservableCollection : ObservableCollection, INotifyCollectionChanged
+ {
+ public EventClearingObservableCollection()
+ {
+ }
+
+ public EventClearingObservableCollection(List list) : base(list)
+ {
+ }
+
+ public EventClearingObservableCollection(IEnumerable collection) : base(collection)
+ {
+ }
+
+ // need to explicitly implement with event accessor syntax in order to override in order to to clear
+ // c# events are weird, they do not behave the same way as other c# things that are 'virtual',
+ // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252
+ // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event
+ private NotifyCollectionChangedEventHandler _changed;
+ event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
+ {
+ add { _changed += value; }
+ remove { _changed -= value; }
+ }
+
+ ///
+ /// Clears all event handlers for the event
+ ///
+ public void ClearCollectionChangedEvents() => _changed = null;
+ }
+}
diff --git a/src/Umbraco.Core/Collections/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs
index c6aedab377..fd9e469f07 100644
--- a/src/Umbraco.Core/Collections/ObservableDictionary.cs
+++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs
@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
namespace Umbraco.Core.Collections
{
+
///
/// An ObservableDictionary
///
@@ -15,7 +18,7 @@ namespace Umbraco.Core.Collections
///
/// The type of elements contained in the BindableCollection
/// The type of the indexing key
- public class ObservableDictionary : ObservableCollection, IReadOnlyDictionary, IDictionary
+ public class ObservableDictionary : ObservableCollection, IReadOnlyDictionary, IDictionary, INotifyCollectionChanged
{
protected Dictionary Indecies { get; }
protected Func KeySelector { get; }
@@ -74,6 +77,22 @@ namespace Umbraco.Core.Collections
#endregion
+ // need to explicitly implement with event accessor syntax in order to override in order to to clear
+ // c# events are weird, they do not behave the same way as other c# things that are 'virtual',
+ // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252
+ // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event
+ private NotifyCollectionChangedEventHandler _changed;
+ event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
+ {
+ add { _changed += value; }
+ remove { _changed -= value; }
+ }
+
+ ///
+ /// Clears all event handlers
+ ///
+ public void ClearCollectionChangedEvents() => _changed = null;
+
public bool ContainsKey(TKey key)
{
return Indecies.ContainsKey(key);
diff --git a/src/Umbraco.Core/Composing/ComponentComposer.cs b/src/Umbraco.Core/Composing/ComponentComposer.cs
index 853d4f4502..fca0161d05 100644
--- a/src/Umbraco.Core/Composing/ComponentComposer.cs
+++ b/src/Umbraco.Core/Composing/ComponentComposer.cs
@@ -1,5 +1,4 @@
-
-using Umbraco.Core.DependencyInjection;
+using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.Composing
{
diff --git a/src/Umbraco.Core/Configuration/IConfigManipulator.cs b/src/Umbraco.Core/Configuration/IConfigManipulator.cs
index bc89b7bd1d..16c5a509f2 100644
--- a/src/Umbraco.Core/Configuration/IConfigManipulator.cs
+++ b/src/Umbraco.Core/Configuration/IConfigManipulator.cs
@@ -1,6 +1,4 @@
-using Umbraco.Core.IO;
-
-namespace Umbraco.Core.Configuration
+namespace Umbraco.Core.Configuration
{
public interface IConfigManipulator
{
@@ -8,5 +6,6 @@ namespace Umbraco.Core.Configuration
void SaveConnectionString(string connectionString, string providerName);
void SaveConfigValue(string itemPath, object value);
void SaveDisableRedirectUrlTracking(bool disable);
+ void SetGlobalId(string id);
}
}
diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
index 55881cd8db..01ffffa190 100644
--- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
@@ -193,8 +193,15 @@ namespace Umbraco.Core.Configuration.Models
public bool ShowDeprecatedPropertyEditors { get; set; } = false;
///
- /// Gets or sets a value for the pate to the login screen background image.
+ /// Gets or sets a value for the path to the login screen background image.
///
public string LoginBackgroundImage { get; set; } = "assets/img/login.jpg";
+
+ ///
+ /// Gets or sets a value for the path to the login screen logo image.
+ ///
+ public string LoginLogoImage { get; set; } = "assets/img/application/umbraco_logo_white.svg";
+
+
}
}
diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
index eb13427d2b..c6e5d92794 100644
--- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
@@ -94,10 +94,15 @@ namespace Umbraco.Core.Configuration.Models
public bool InstallMissingDatabase { get; set; } = false;
///
- /// Gets or sets a value indicating whether to install the database when it is empty.
+ /// Gets or sets a value indicating whether unattended installs are enabled.
///
- public bool InstallEmptyDatabase { get; set; } = false;
-
+ ///
+ /// By default, when a database connection string is configured and it is possible to connect to
+ /// the database, but the database is empty, the runtime enters the Install level.
+ /// If this option is set to true an unattended install will be performed and the runtime enters
+ /// the Run level.
+ ///
+ public bool InstallUnattended { get; set; } = false;
///
/// Gets or sets a value indicating whether to disable the election for a single server.
///
@@ -112,6 +117,7 @@ namespace Umbraco.Core.Configuration.Models
/// Gets or sets a value for the main dom lock.
///
public string MainDomLock { get; set; } = string.Empty;
+ public string Id { get; set; } = string.Empty;
///
/// Gets or sets a value for the path to the no content view.
diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs
index a60f29c39d..451ac5d438 100644
--- a/src/Umbraco.Core/Constants-Configuration.cs
+++ b/src/Umbraco.Core/Constants-Configuration.cs
@@ -16,10 +16,10 @@
public const string ConfigCorePrefix = ConfigPrefix + "Core:";
public const string ConfigCustomErrorsPrefix = ConfigPrefix + "CustomErrors:";
public const string ConfigGlobalPrefix = ConfigPrefix + "Global:";
+ public const string ConfigGlobalId = ConfigGlobalPrefix + "Id";
public const string ConfigHostingPrefix = ConfigPrefix + "Hosting:";
public const string ConfigModelsBuilderPrefix = ConfigPrefix + "ModelsBuilder:";
public const string ConfigSecurityPrefix = ConfigPrefix + "Security:";
-
public const string ConfigContentNotificationsEmail = ConfigContentNotificationsPrefix + "Email";
public const string ConfigContentMacroErrors = ConfigContentPrefix + "MacroErrors";
public const string ConfigGlobalUseHttps = ConfigGlobalPrefix + "UseHttps";
diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs
index 984bc495b0..5a78b62f5b 100644
--- a/src/Umbraco.Core/Constants-SqlTemplates.cs
+++ b/src/Umbraco.Core/Constants-SqlTemplates.cs
@@ -14,7 +14,16 @@
public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode";
public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId";
}
+ public static class RelationRepository
+ {
+ public const string DeleteByParentAll = "Umbraco.Core.RelationRepository.DeleteByParent";
+ public const string DeleteByParentIn = "Umbraco.Core.RelationRepository.DeleteByParentIn";
+ }
+ public static class DataTypeRepository
+ {
+ public const string EnsureUniqueNodeName = "Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName";
+ }
}
}
}
diff --git a/src/Umbraco.Core/Constants-SvgSanitizer.cs b/src/Umbraco.Core/Constants-SvgSanitizer.cs
index 447ea66668..c92b9f56c7 100644
--- a/src/Umbraco.Core/Constants-SvgSanitizer.cs
+++ b/src/Umbraco.Core/Constants-SvgSanitizer.cs
@@ -17,7 +17,7 @@ namespace Umbraco.Core
///
/// Allowlist for SVG tabs.
///
- public static readonly IList Tags = new [] { "a", "altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "discard", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hatch", "hatchpath", "hkern", "image", "line", "linearGradient", "marker", "mask", "mesh", "meshgradient", "meshpatch", "meshrow", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "solidcolor", "stop", "style", "svg", "switch", "symbol", "text", "textPath", "title", "tref", "tspan", "unknown", "use", "view", "vkern" };
+ public static readonly IList Tags = new [] { "a", "altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "discard", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hatch", "hatchpath", "hkern", "image", "line", "linearGradient", "marker", "mask", "mesh", "meshgradient", "meshpatch", "meshrow", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "solidcolor", "stop", "svg", "switch", "symbol", "text", "textPath", "title", "tref", "tspan", "unknown", "use", "view", "vkern" };
}
}
}
diff --git a/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs
new file mode 100644
index 0000000000..6f672d17cd
--- /dev/null
+++ b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Exceptions
+{
+ ///
+ /// An exception that is thrown if an unattended installation occurs.
+ ///
+ [Serializable]
+ public class UnattendedInstallException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UnattendedInstallException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public UnattendedInstallException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message
+ /// and a reference to the inner exception which is the cause of this exception.
+ ///
+ /// The message that describes the error.
+ /// The inner exception, or null.
+ public UnattendedInstallException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that holds the serialized object data about the exception being thrown.
+ /// The that contains contextual information about the source or destination.
+ protected UnattendedInstallException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs
index c8151d076c..92e9156f2f 100644
--- a/src/Umbraco.Core/IO/SystemFiles.cs
+++ b/src/Umbraco.Core/IO/SystemFiles.cs
@@ -9,8 +9,6 @@ namespace Umbraco.Core.IO
{
public static string TinyMceConfig => Constants.SystemDirectories.Config + "/tinyMceConfig.config";
- public static string TelemetricsIdentifier => Constants.SystemDirectories.Data + "/telemetrics-id.umb";
-
// TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache
public static string GetContentCacheXml(IHostingEnvironment hostingEnvironment)
{
diff --git a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
new file mode 100644
index 0000000000..01d8e428c7
--- /dev/null
+++ b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Web.Install.Models;
+
+namespace Umbraco.Web.Install.InstallSteps
+{
+ [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade,
+ "TelemetryIdConfiguration", 0, "",
+ PerformsAppRestart = false)]
+ public class TelemetryIdentifierStep : InstallSetupStep
public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public void ClearCollectionChangedEvents() => CollectionChanged = null;
+
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs
index 2e6da5d837..022fb6ed03 100644
--- a/src/Umbraco.Core/Models/PropertyGroup.cs
+++ b/src/Umbraco.Core/Models/PropertyGroup.cs
@@ -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
}
diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs
index f5b3012b1a..11f4e470ee 100644
--- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs
+++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs
@@ -160,6 +160,11 @@ namespace Umbraco.Core.Models
public event NotifyCollectionChangedEventHandler CollectionChanged;
+ ///
+ /// Clears all event handlers
+ ///
+ public void ClearCollectionChangedEvents() => CollectionChanged = null;
+
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs
index bca7e88e03..132c80e7d1 100644
--- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs
+++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs
@@ -165,7 +165,11 @@ namespace Umbraco.Core.Models
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
-
+
+ ///
+ /// Clears all event handlers
+ ///
+ public void ClearCollectionChangedEvents() => CollectionChanged = null;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs
index b7f9caed44..e48e3bd711 100644
--- a/src/Umbraco.Core/Models/PublicAccessEntry.cs
+++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs
@@ -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 _ruleCollection;
+ private readonly EventClearingObservableCollection _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(ruleCollection);
+ _ruleCollection = new EventClearingObservableCollection(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(ruleCollection);
+ _ruleCollection = new EventClearingObservableCollection(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
}
}
diff --git a/src/Umbraco.Core/Models/Range.cs b/src/Umbraco.Core/Models/Range.cs
index 83afa11658..108d564665 100644
--- a/src/Umbraco.Core/Models/Range.cs
+++ b/src/Umbraco.Core/Models/Range.cs
@@ -1,52 +1,130 @@
using System;
+using System.Globalization;
+
namespace Umbraco.Core.Models
{
- // The Range class. Adapted from http://stackoverflow.com/questions/5343006/is-there-a-c-sharp-type-for-representing-an-integer-range
- /// Generic parameter.
- public class Range where T : IComparable
+ ///
+ /// Represents a range with a minimum and maximum value.
+ ///
+ /// The type of the minimum and maximum values.
+ ///
+ public class Range : IEquatable>
+ where T : IComparable
{
- /// Minimum value of the range.
+ ///
+ /// Gets or sets the minimum value.
+ ///
+ ///
+ /// The minimum value.
+ ///
public T Minimum { get; set; }
- /// Maximum value of the range.
+ ///
+ /// Gets or sets the maximum value.
+ ///
+ ///
+ /// The maximum value.
+ ///
public T Maximum { get; set; }
- /// Presents the Range in readable format.
- /// String representation of the Range
- public override string ToString()
- {
- return string.Format("{0},{1}", this.Minimum, this.Maximum);
- }
+ ///
+ /// Returns a that represents this instance.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString() => this.ToString("{0},{1}", CultureInfo.InvariantCulture);
- /// Determines if the range is valid.
- /// True if range is valid, else false
- public bool IsValid()
- {
- return this.Minimum.CompareTo(this.Maximum) <= 0;
- }
+ ///
+ /// Returns a that represents this instance.
+ ///
+ /// A composite format string for a single value (minimum and maximum are equal). Use {0} for the minimum and {1} for the maximum value.
+ /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value.
+ /// An object that supplies culture-specific formatting information.
+ ///
+ /// A that represents this instance.
+ ///
+ public string ToString(string format, string formatRange, IFormatProvider provider = null) => this.ToString(this.Minimum.CompareTo(this.Maximum) == 0 ? format : formatRange, provider);
- /// Determines if the provided value is inside the range.
- /// The value to test
- /// True if the value is inside Range, else false
- public bool ContainsValue(T value)
- {
- return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0);
- }
+ ///
+ /// Returns a that represents this instance.
+ ///
+ /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value.
+ /// An object that supplies culture-specific formatting information.
+ ///
+ /// A that represents this instance.
+ ///
+ public string ToString(string format, IFormatProvider provider = null) => string.Format(provider, format, this.Minimum, this.Maximum);
- /// Determines if this Range is inside the bounds of another range.
- /// The parent range to test on
- /// True if range is inclusive, else false
- public bool IsInsideRange(Range range)
- {
- return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
- }
+ ///
+ /// Determines whether this range is valid (the minimum value is lower than or equal to the maximum value).
+ ///
+ ///
+ /// true if this range is valid; otherwise, false.
+ ///
+ public bool IsValid() => this.Minimum.CompareTo(this.Maximum) <= 0;
- /// Determines if another range is inside the bounds of this range.
- /// The child range to test
- /// True if range is inside, else false
- public bool ContainsRange(Range range)
- {
- return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
- }
+ ///
+ /// Determines whether this range contains the specified value.
+ ///
+ /// The value.
+ ///
+ /// true if this range contains the specified value; otherwise, false.
+ ///
+ public bool ContainsValue(T value) => this.Minimum.CompareTo(value) <= 0 && value.CompareTo(this.Maximum) <= 0;
+
+ ///
+ /// Determines whether this range is inside the specified range.
+ ///
+ /// The range.
+ ///
+ /// true if this range is inside the specified range; otherwise, false.
+ ///
+ public bool IsInsideRange(Range range) => this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
+
+ ///
+ /// Determines whether this range contains the specified range.
+ ///
+ /// The range.
+ ///
+ /// true if this range contains the specified range; otherwise, false.
+ ///
+ public bool ContainsRange(Range range) => this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
+
+ ///
+ /// Determines whether the specified , is equal to this instance.
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ public override bool Equals(object obj) => obj is Range other && this.Equals(other);
+
+ ///
+ /// Indicates whether the current object is equal to another object of the same type.
+ ///
+ /// An object to compare with this object.
+ ///
+ /// if the current object is equal to the parameter; otherwise, .
+ ///
+ public bool Equals(Range other) => other != null && this.Equals(other.Minimum, other.Maximum);
+
+ ///
+ /// Determines whether the specified and values are equal to this instance values.
+ ///
+ /// The minimum value.
+ /// The maximum value.
+ ///
+ /// true if the specified and values are equal to this instance values; otherwise, false.
+ ///
+ public bool Equals(T minimum, T maximum) => this.Minimum.CompareTo(minimum) == 0 && this.Maximum.CompareTo(maximum) == 0;
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public override int GetHashCode() => (this.Minimum, this.Maximum).GetHashCode();
}
}
diff --git a/src/Umbraco.Core/Scheduling/RecurringTaskBase.cs b/src/Umbraco.Core/Scheduling/RecurringTaskBase.cs
index 58df54a07e..4544711946 100644
--- a/src/Umbraco.Core/Scheduling/RecurringTaskBase.cs
+++ b/src/Umbraco.Core/Scheduling/RecurringTaskBase.cs
@@ -15,9 +15,29 @@ namespace Umbraco.Web.Scheduling
public abstract class RecurringTaskBase : LatchedBackgroundTaskBase
{
private readonly IBackgroundTaskRunner _runner;
- private readonly int _periodMilliseconds;
+ private readonly long _periodMilliseconds;
private readonly Timer _timer;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The task runner.
+ /// The delay.
+ /// The period.
+ /// The task will repeat itself periodically. Use this constructor to create a new task.
+ protected RecurringTaskBase(IBackgroundTaskRunner runner, long delayMilliseconds, long periodMilliseconds)
+ {
+ _runner = runner;
+ _periodMilliseconds = periodMilliseconds;
+
+ // note
+ // must use the single-parameter constructor on Timer to avoid it from being GC'd
+ // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
+
+ _timer = new Timer(_ => Release());
+ _timer.Change(delayMilliseconds, 0);
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
index 8d21c21276..d03820b53e 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
@@ -37,7 +37,7 @@ namespace Umbraco.Core.Services
/// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
/// be looked up via the db, they need to be passed in.
///
- /// Wether the composite content types should be applicable for an element type
+ /// Whether the composite content types should be applicable for an element type
///
public static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(this IContentTypeService ctService,
IContentTypeComposition source,
diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs
index ef826014be..2e5f8630d4 100644
--- a/src/Umbraco.Core/Services/IRuntimeState.cs
+++ b/src/Umbraco.Core/Services/IRuntimeState.cs
@@ -57,5 +57,6 @@ namespace Umbraco.Core
void Configure(RuntimeLevel level, RuntimeLevelReason reason);
+ void DoUnattendedInstall();
}
}
diff --git a/src/Umbraco.Core/Telemetry/TelemetryMarkerComponent.cs b/src/Umbraco.Core/Telemetry/TelemetryMarkerComponent.cs
deleted file mode 100644
index 157cacd618..0000000000
--- a/src/Umbraco.Core/Telemetry/TelemetryMarkerComponent.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System;
-using System.IO;
-using Microsoft.Extensions.Logging;
-using Umbraco.Core;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Hosting;
-using Umbraco.Core.IO;
-
-namespace Umbraco.Web.Telemetry
-{
- public class TelemetryMarkerComponent : IComponent
- {
- private readonly ILogger _logger;
- private readonly IRuntimeState _runtime;
- private readonly IHostingEnvironment _hostingEnvironment;
-
- public TelemetryMarkerComponent(ILogger logger, IRuntimeState runtime, IHostingEnvironment hostingEnvironment)
- {
- _logger = logger;
- _runtime = runtime;
- _hostingEnvironment = hostingEnvironment;
- }
-
- public void Initialize()
- {
- if (_runtime.Level != RuntimeLevel.Install && _runtime.Level != RuntimeLevel.Upgrade)
- {
- return;
- }
-
- var telemetricsFilePath = _hostingEnvironment.MapPathContentRoot(SystemFiles.TelemetricsIdentifier);
-
- // Verify file does not exist already (if we are upgrading)
- // In a clean install we know it would not exist
- // If the site is upgraded and the file was removed it would re-create one
- // NOTE: If user removed the marker file to opt out it would re-create a new guid marker file & potentially skew
- if (_runtime.Level == RuntimeLevel.Upgrade && File.Exists(telemetricsFilePath))
- {
- _logger.LogWarning("When upgrading the anonymous telemetry file already existed on disk at {filePath}", telemetricsFilePath);
- return;
- }
- if (_runtime.Level == RuntimeLevel.Install && File.Exists(telemetricsFilePath))
- {
- // No need to log for when level is install if file exists (As this component hit several times during install process)
- return;
- }
-
- // We are a clean install or an upgrade without the marker file
- // Generate GUID
- var telemetrySiteIdentifier = Guid.NewGuid();
-
- // Write file contents
- try
- {
- File.WriteAllText(telemetricsFilePath, telemetrySiteIdentifier.ToString());
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Unable to create telemetry file at {filePath}", telemetricsFilePath);
- }
-
-
-
- }
-
- public void Terminate()
- {
- }
- }
-}
diff --git a/src/Umbraco.Core/Telemetry/TelemetryMarkerComposer.cs b/src/Umbraco.Core/Telemetry/TelemetryMarkerComposer.cs
deleted file mode 100644
index ccb66c7b2a..0000000000
--- a/src/Umbraco.Core/Telemetry/TelemetryMarkerComposer.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Umbraco.Core;
-using Umbraco.Core.Composing;
-
-namespace Umbraco.Web.Telemetry
-{
- public class TelemetryMarkerComposer : ComponentComposer, ICoreComposer
- { }
-}
diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
index a3a43d2b93..0d6be8717a 100644
--- a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
+++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
@@ -42,6 +42,7 @@ namespace Umbraco.Core.Configuration
SaveJson(provider, json);
}
+
public void SaveConfigValue(string key, object value)
{
var provider = GetJsonConfigurationProvider();
@@ -79,6 +80,40 @@ namespace Umbraco.Core.Configuration
SaveJson(provider, json);
}
+ public void SetGlobalId(string id)
+ {
+ var provider = GetJsonConfigurationProvider();
+
+ var json = GetJson(provider);
+
+ var item = GetGlobalIdItem(id);
+
+ json.Merge(item, new JsonMergeSettings());
+
+ SaveJson(provider, json);
+ }
+
+ private object GetGlobalIdItem(string id)
+ {
+ JTokenWriter writer = new JTokenWriter();
+
+ writer.WriteStartObject();
+ writer.WritePropertyName("Umbraco");
+ writer.WriteStartObject();
+ writer.WritePropertyName("CMS");
+ writer.WriteStartObject();
+ writer.WritePropertyName("Global");
+ writer.WriteStartObject();
+ writer.WritePropertyName("Id");
+ writer.WriteValue(id);
+ writer.WriteEndObject();
+ writer.WriteEndObject();
+ writer.WriteEndObject();
+ writer.WriteEndObject();
+
+ return writer.Token;
+ }
+
private JToken GetDisableRedirectUrlItem(string value)
{
JTokenWriter writer = new JTokenWriter();
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 94c1e3dcfa..8ce9839f6b 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -194,11 +194,12 @@ namespace Umbraco.Infrastructure.DependencyInjection
var hostingEnvironment = factory.GetRequiredService();
var dbCreator = factory.GetRequiredService();
+ var databaseSchemaCreatorFactory = factory.GetRequiredService();
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var loggerFactory = factory.GetRequiredService();
return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
- ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment)
+ ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment, databaseSchemaCreatorFactory)
: new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment);
});
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
index e5bdd844d8..1e40831f75 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
@@ -17,6 +17,7 @@ namespace Umbraco.Infrastructure.DependencyInjection
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+ builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs
index 95b09d5498..7ce1fffa0c 100644
--- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs
@@ -1,15 +1,14 @@
using Newtonsoft.Json;
using System;
-using System.IO;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
-using Umbraco.Core.Hosting;
-using Umbraco.Core.IO;
+using Umbraco.Core.Configuration.Models;
using Umbraco.Infrastructure.HostedServices;
namespace Umbraco.Web.Telemetry
@@ -17,71 +16,43 @@ namespace Umbraco.Web.Telemetry
public class ReportSiteTask : RecurringHostedServiceBase
{
private readonly ILogger _logger;
- private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoVersion _umbracoVersion;
+ private readonly IOptions _globalSettings;
private static HttpClient s_httpClient;
public ReportSiteTask(
ILogger logger,
- IHostingEnvironment hostingEnvironment,
- IUmbracoVersion umbracoVersion)
+ IUmbracoVersion umbracoVersion,
+ IOptions globalSettings)
: base(TimeSpan.FromDays(1), TimeSpan.FromMinutes(1))
{
_logger = logger;
- _hostingEnvironment = hostingEnvironment;
_umbracoVersion = umbracoVersion;
+ _globalSettings = globalSettings;
s_httpClient = new HttpClient();
}
///
- /// Runs the background task to send the anynomous ID
+ /// Runs the background task to send the anonymous ID
/// to telemetry service
///
internal override async Task PerformExecuteAsync(object state)
{
- // Try & find file at '/umbraco/telemetrics-id.umb'
- var telemetricsFilePath = _hostingEnvironment.MapPathContentRoot(SystemFiles.TelemetricsIdentifier);
-
- if (File.Exists(telemetricsFilePath) == false)
- {
- // Some users may have decided to not be tracked by deleting/removing the marker file
- _logger.LogWarning("No telemetry marker file found at '{filePath}' and will not report site to telemetry service", telemetricsFilePath);
-
- return;
- }
-
-
- string telemetricsFileContents;
- try
- {
- // Open file & read its contents
- // It may throw due to file permissions or file locking
- telemetricsFileContents = File.ReadAllText(telemetricsFilePath);
- }
- catch (Exception ex)
- {
- // Silently swallow ex - but lets log it (ReadAllText throws a ton of different types of ex)
- // Hence the use of general exception type
- _logger.LogError(ex, "Error in reading file contents of telemetry marker file found at '{filePath}'", telemetricsFilePath);
-
- // Exit out early, but mark this task to be repeated in case its a file lock so it can be rechecked the next time round
- return;
- }
-
+ // Try & get a value stored in umbracoSettings.config on the backoffice XML element ID attribute
+ var backofficeIdentifierRaw = _globalSettings.Value.Id;
// Parse as a GUID & verify its a GUID and not some random string
// In case of users may have messed or decided to empty the file contents or put in something random
- if (Guid.TryParse(telemetricsFileContents, out var telemetrySiteIdentifier) == false)
+ if (Guid.TryParse(backofficeIdentifierRaw, out var telemetrySiteIdentifier) == false)
{
- // Some users may have decided to mess with file contents
- _logger.LogWarning("The telemetry marker file found at '{filePath}' with '{telemetrySiteId}' is not a valid identifier for the telemetry service", telemetricsFilePath, telemetrySiteIdentifier);
+ // Some users may have decided to mess with the XML attribute and put in something else
+ _logger.LogWarning("No telemetry marker found");
return;
}
try
{
-
// Send data to LIVE telemetry
s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/");
@@ -112,11 +83,10 @@ namespace Umbraco.Web.Telemetry
{
// Silently swallow
// The user does not need the logs being polluted if our service has fallen over or is down etc
- // Hence only loggigng this at a more verbose level (Which users should not be using in prod)
+ // Hence only logging this at a more verbose level (which users should not be using in production)
_logger.LogDebug("There was a problem sending a request to the Umbraco telemetry service");
}
}
-
[DataContract]
private class TelemetryReportData
{
diff --git a/src/Umbraco.Infrastructure/Install/InstallHelper.cs b/src/Umbraco.Infrastructure/Install/InstallHelper.cs
index 1538187528..b8b5371457 100644
--- a/src/Umbraco.Infrastructure/Install/InstallHelper.cs
+++ b/src/Umbraco.Infrastructure/Install/InstallHelper.cs
@@ -131,7 +131,7 @@ namespace Umbraco.Web.Install
return true;
}
- return _databaseBuilder.HasSomeNonDefaultUser() == false;
+ return _databaseBuilder.IsUmbracoInstalled() == false;
}
}
diff --git a/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs b/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs
index 523cd35a27..4b352190b0 100644
--- a/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs
+++ b/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs
@@ -21,6 +21,7 @@ namespace Umbraco.Web.Install
a.OfType().First(),
a.OfType().First(),
a.OfType().First(),
+ a.OfType().First(),
a.OfType().First(),
a.OfType().First(),
a.OfType().First(),
diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs
index b746011494..91c78457c0 100644
--- a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs
+++ b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs
@@ -9,6 +9,7 @@ using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Security;
+using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Infrastructure.Security;
@@ -35,6 +36,7 @@ namespace Umbraco.Web.Install.InstallSteps
private readonly ConnectionStrings _connectionStrings;
private readonly ICookieManager _cookieManager;
private readonly IBackOfficeUserManager _userManager;
+ private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
public NewInstallStep(
IUserService userService,
@@ -43,7 +45,8 @@ namespace Umbraco.Web.Install.InstallSteps
IOptions securitySettings,
IOptions connectionStrings,
ICookieManager cookieManager,
- IBackOfficeUserManager userManager)
+ IBackOfficeUserManager userManager,
+ IDbProviderFactoryCreator dbProviderFactoryCreator)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder));
@@ -52,6 +55,7 @@ namespace Umbraco.Web.Install.InstallSteps
_connectionStrings = connectionStrings.Value ?? throw new ArgumentNullException(nameof(connectionStrings));
_cookieManager = cookieManager;
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
+ _dbProviderFactoryCreator = dbProviderFactoryCreator ?? throw new ArgumentNullException(nameof(dbProviderFactoryCreator));
}
public override async Task ExecuteAsync(UserModel user)
@@ -121,26 +125,89 @@ namespace Umbraco.Web.Install.InstallSteps
{
get
{
- return RequiresExecution(null)
- //the user UI
- ? "user"
- //the continue install UI
- : "continueinstall";
+ return ShowView()
+ // the user UI
+ ? "user"
+ // continue install UI
+ : "continueinstall";
}
}
+ private InstallState GetInstallState()
+ {
+ var installState = InstallState.Unknown;
+
+ var databaseSettings = _connectionStrings.UmbracoConnectionString;
+
+ var hasConnString = databaseSettings != null && _databaseBuilder.IsDatabaseConfigured;
+ if (hasConnString)
+ {
+ installState = (installState | InstallState.HasConnectionString) & ~InstallState.Unknown;
+ }
+
+ var connStringConfigured = databaseSettings.IsConnectionStringConfigured();
+ if (connStringConfigured)
+ {
+ installState = (installState | InstallState.ConnectionStringConfigured) & ~InstallState.Unknown;
+ }
+
+ var factory = _dbProviderFactoryCreator.CreateFactory(databaseSettings.ProviderName);
+ var canConnect = connStringConfigured && DbConnectionExtensions.IsConnectionAvailable(databaseSettings.ConnectionString, factory);
+ if (canConnect)
+ {
+ installState = (installState | InstallState.CanConnect) & ~InstallState.Unknown;
+ }
+
+ var umbracoInstalled = canConnect ? _databaseBuilder.IsUmbracoInstalled() : false;
+ if (umbracoInstalled)
+ {
+ installState = (installState | InstallState.UmbracoInstalled) & ~InstallState.Unknown;
+ }
+
+ var hasNonDefaultUser = umbracoInstalled ? _databaseBuilder.HasSomeNonDefaultUser() : false;
+ if (hasNonDefaultUser)
+ {
+ installState = (installState | InstallState.HasNonDefaultUser) & ~InstallState.Unknown;
+ }
+
+ return installState;
+ }
+
+ private bool ShowView()
+ {
+ var installState = GetInstallState();
+
+ return installState.HasFlag(InstallState.Unknown)
+ || !installState.HasFlag(InstallState.UmbracoInstalled);
+ }
+
public override bool RequiresExecution(UserModel model)
{
- //now we have to check if this is really a new install, the db might be configured and might contain data
- var databaseSettings = _connectionStrings.UmbracoConnectionString;
- if (databaseSettings.IsConnectionStringConfigured() && _databaseBuilder.IsDatabaseConfigured)
- return _databaseBuilder.HasSomeNonDefaultUser() == false;
+ var installState = GetInstallState();
- // In this one case when it's a brand new install and nothing has been configured, make sure the
- // back office cookie is cleared so there's no old cookies lying around causing problems
- _cookieManager.ExpireCookie(_securitySettings.AuthCookieName);
+ if (installState.HasFlag(InstallState.Unknown))
+ {
+ // In this one case when it's a brand new install and nothing has been configured, make sure the
+ // back office cookie is cleared so there's no old cookies lying around causing problems
+ _cookieManager.ExpireCookie(_securitySettings.AuthCookieName);
+ }
- return true;
+ return installState.HasFlag(InstallState.Unknown)
+ || !installState.HasFlag(InstallState.HasNonDefaultUser);
+ }
+
+ [Flags]
+ private enum InstallState : short
+ {
+ // This is an easy way to avoid 0 enum assignment and not worry about
+ // manual calcs. https://www.codeproject.com/Articles/396851/Ending-the-Great-Debate-on-Enum-Flags
+ Unknown = 1,
+ HasVersion = 1 << 1,
+ HasConnectionString = 1 << 2,
+ ConnectionStringConfigured = 1 << 3,
+ CanConnect = 1 << 4,
+ UmbracoInstalled = 1 << 5,
+ HasNonDefaultUser = 1 << 6
}
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
index 98e9bcb4bb..541896548c 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
@@ -29,9 +29,9 @@ namespace Umbraco.Core.Migrations.Install
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
- private readonly IUmbracoVersion _umbracoVersion;
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
private readonly IConfigManipulator _configManipulator;
+ private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
private DatabaseSchemaResult _databaseSchemaValidationResult;
@@ -47,9 +47,9 @@ namespace Umbraco.Core.Migrations.Install
IMigrationBuilder migrationBuilder,
IKeyValueService keyValueService,
IHostingEnvironment hostingEnvironment,
- IUmbracoVersion umbracoVersion,
IDbProviderFactoryCreator dbProviderFactoryCreator,
- IConfigManipulator configManipulator)
+ IConfigManipulator configManipulator,
+ DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
_scopeProvider = scopeProvider;
_databaseFactory = databaseFactory;
@@ -59,9 +59,9 @@ namespace Umbraco.Core.Migrations.Install
_migrationBuilder = migrationBuilder;
_keyValueService = keyValueService;
_hostingEnvironment = hostingEnvironment;
- _umbracoVersion = umbracoVersion;
_dbProviderFactoryCreator = dbProviderFactoryCreator;
_configManipulator = configManipulator;
+ _databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
}
#region Status
@@ -133,6 +133,14 @@ namespace Umbraco.Core.Migrations.Install
}
}
+ internal bool IsUmbracoInstalled()
+ {
+ using (var scope = _scopeProvider.CreateScope(autoComplete: true))
+ {
+ return scope.Database.IsUmbracoInstalled();
+ }
+ }
+
#endregion
#region Configure Connection String
@@ -315,14 +323,15 @@ namespace Umbraco.Core.Migrations.Install
private DatabaseSchemaResult ValidateSchema(IScope scope)
{
if (_databaseFactory.Initialized == false)
- return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax);
+ return new DatabaseSchemaResult();
if (_databaseSchemaValidationResult != null)
return _databaseSchemaValidationResult;
- var database = scope.Database;
- var dbSchema = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger(), _loggerFactory, _umbracoVersion);
- _databaseSchemaValidationResult = dbSchema.ValidateSchema();
+ _databaseSchemaValidationResult = scope.Database.ValidateSchema();
+
+ scope.Complete();
+
return _databaseSchemaValidationResult;
}
@@ -361,15 +370,14 @@ namespace Umbraco.Core.Migrations.Install
var schemaResult = ValidateSchema();
var hasInstalledVersion = schemaResult.DetermineHasInstalledVersion();
- //var installedSchemaVersion = schemaResult.DetermineInstalledVersion();
- //var hasInstalledVersion = !installedSchemaVersion.Equals(new Version(0, 0, 0));
+ //If the determined version is "empty" its a new install - otherwise upgrade the existing
if (!hasInstalledVersion)
{
if (_runtime.Level == RuntimeLevel.Run)
throw new Exception("Umbraco is already configured!");
- var creator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger(), _loggerFactory, _umbracoVersion);
+ var creator = _databaseSchemaCreatorFactory.Create(database);
creator.InitializeDatabaseSchema();
message = message + "Installation completed!
";
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
index 5bca64e1e1..ed6e684cc6 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
@@ -25,10 +25,15 @@ namespace Umbraco.Core.Migrations.Install
public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger, ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion)
{
- _database = database;
- _logger = logger;
- _loggerFactory = loggerFactory;
- _umbracoVersion = umbracoVersion;
+ _database = database ?? throw new ArgumentNullException(nameof(database));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
+ _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion));
+
+ if (_database?.SqlContext?.SqlSyntax == null)
+ {
+ throw new InvalidOperationException("No SqlContext has been assigned to the database");
+ }
}
private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax;
@@ -149,7 +154,7 @@ namespace Umbraco.Core.Migrations.Install
internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables)
{
- var result = new DatabaseSchemaResult(SqlSyntax);
+ var result = new DatabaseSchemaResult();
result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database)
.Select(x => new DbIndexDefinition(x)));
@@ -451,14 +456,14 @@ namespace Umbraco.Core.Migrations.Install
}
//Execute the Create Table sql
- var created = _database.Execute(new Sql(createSql));
- _logger.LogInformation("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql);
+ _database.Execute(new Sql(createSql));
+ _logger.LogInformation("Create Table {TableName}: \n {Sql}", tableName, createSql);
//If any statements exists for the primary key execute them here
if (string.IsNullOrEmpty(createPrimaryKeySql) == false)
{
- var createdPk = _database.Execute(new Sql(createPrimaryKeySql));
- _logger.LogInformation("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql);
+ _database.Execute(new Sql(createPrimaryKeySql));
+ _logger.LogInformation("Create Primary Key:\n {Sql}", createPrimaryKeySql);
}
if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity))
@@ -475,15 +480,15 @@ namespace Umbraco.Core.Migrations.Install
//Loop through index statements and execute sql
foreach (var sql in indexSql)
{
- var createdIndex = _database.Execute(new Sql(sql));
- _logger.LogInformation("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql);
+ _database.Execute(new Sql(sql));
+ _logger.LogInformation("Create Index:\n {Sql}", sql);
}
//Loop through foreignkey statements and execute sql
foreach (var sql in foreignSql)
{
- var createdFk = _database.Execute(new Sql(sql));
- _logger.LogInformation("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql);
+ _database.Execute(new Sql(sql));
+ _logger.LogInformation("Create Foreign Key:\n {Sql}", sql);
}
if (overwrite)
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreatorFactory.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreatorFactory.cs
new file mode 100644
index 0000000000..935ede3ab5
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreatorFactory.cs
@@ -0,0 +1,31 @@
+using Microsoft.Extensions.Logging;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Persistence;
+
+namespace Umbraco.Core.Migrations.Install
+{
+ ///
+ /// Creates the initial database schema during install.
+ ///
+ public class DatabaseSchemaCreatorFactory
+ {
+ private readonly ILogger _logger;
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly IUmbracoVersion _umbracoVersion;
+
+ public DatabaseSchemaCreatorFactory(
+ ILogger logger,
+ ILoggerFactory loggerFactory,
+ IUmbracoVersion umbracoVersion)
+ {
+ _logger = logger;
+ _loggerFactory = loggerFactory;
+ _umbracoVersion = umbracoVersion;
+ }
+
+ public DatabaseSchemaCreator Create(IUmbracoDatabase database)
+ {
+ return new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion);
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaResult.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaResult.cs
index e7b7b479ec..a153ba7b41 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaResult.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaResult.cs
@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
-using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Migrations.Install
{
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Migrations.Install
///
public class DatabaseSchemaResult
{
- public DatabaseSchemaResult(ISqlSyntaxProvider sqlSyntax)
+ public DatabaseSchemaResult()
{
Errors = new List>();
TableDefinitions = new List();
diff --git a/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs b/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs
index 1b0d1d9c2b..3849ae6ebd 100644
--- a/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using NPoco;
+using Umbraco.Core.Migrations.Install;
namespace Umbraco.Core.Persistence
{
@@ -25,5 +26,7 @@ namespace Umbraco.Core.Persistence
bool EnableSqlCount { get; set; }
int SqlCount { get; }
int BulkInsertRecords(IEnumerable records);
+ bool IsUmbracoInstalled();
+ DatabaseSchemaResult ValidateSchema();
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 7b90efd4ae..bd947260ce 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -947,7 +947,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.EnsureUniqueNodeName, tsql => tsql
.Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name"))
.From()
- .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId")));
+ .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))
+ );
var sql = template.Sql(NodeObjectTypeId, parentId);
var names = Database.Fetch(sql);
@@ -957,28 +958,43 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected virtual int GetNewChildSortOrder(int parentId, int first)
{
- var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql =>
- tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From().Where(x => x.ParentId == SqlTemplate.Arg("parentId") && x.NodeObjectType == NodeObjectTypeId)
+ var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql => tsql
+ .Select("MAX(sortOrder)")
+ .From()
+ .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))
);
- return Database.ExecuteScalar(template.Sql(new { parentId })) + 1;
+ var sql = template.Sql(NodeObjectTypeId, parentId);
+ var sortOrder = Database.ExecuteScalar(sql);
+
+ return (sortOrder + 1) ?? first;
}
protected virtual NodeDto GetParentNodeDto(int parentId)
{
- var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql =>
- tsql.Select().From().Where(x => x.NodeId == SqlTemplate.Arg("parentId"))
+ var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql => tsql
+ .Select()
+ .From()
+ .Where(x => x.NodeId == SqlTemplate.Arg("parentId"))
);
- return Database.Fetch(template.Sql(parentId)).First();
+ var sql = template.Sql(parentId);
+ var nodeDto = Database.First(sql);
+
+ return nodeDto;
}
protected virtual int GetReservedId(Guid uniqueId)
{
- var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql =>
- tsql.Select(x => x.NodeId).From().Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation)
+ var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql => tsql
+ .Select(x => x.NodeId)
+ .From()
+ .Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation)
);
- var id = Database.ExecuteScalar(template.Sql(new { uniqueId = uniqueId }));
+
+ var sql = template.Sql(new { uniqueId });
+ var id = Database.ExecuteScalar(sql);
+
return id ?? 0;
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 6554782d24..c5c38d29bc 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -467,8 +467,14 @@ AND umbracoNode.id <> @id",
// The composed property is only considered segment variant when the base content type is also segment variant.
// Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
var target = newContentTypeVariation & composedPropertyType.Variations;
+ // Determine the previous variation
+ // We have to compare with the old content type variation because the composed property might already have changed
+ // Example: A property with variations in an element type with variations is used in a document without
+ // when you enable variations the property has already enabled variations from the element type,
+ // but it's still a change from nothing because the document did not have variations, but it does now.
+ var from = oldContentTypeVariation & composedPropertyType.Variations;
- propertyTypeVariationChanges[composedPropertyType.Id] = (composedPropertyType.Variations, target);
+ propertyTypeVariationChanges[composedPropertyType.Id] = (from, target);
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs
index 1f614e7647..b50f46e9a6 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs
@@ -313,7 +313,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private string EnsureUniqueNodeName(string nodeName, int id = 0)
{
- var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql
+ var template = SqlContext.Templates.Get(Constants.SqlTemplates.DataTypeRepository.EnsureUniqueNodeName, tsql => tsql
.Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name"))
.From()
.Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")));
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
index 4299d50f15..80ca2edd11 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
@@ -314,17 +314,51 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public void DeleteByParent(int parentId, params string[] relationTypeAliases)
{
- var subQuery = Sql().Select(x => x.Id)
- .From()
- .InnerJoin().On(x => x.RelationType, x => x.Id)
- .Where(x => x.ParentId == parentId);
-
- if (relationTypeAliases.Length > 0)
+ if (Database.DatabaseType.IsSqlCe())
{
- subQuery.WhereIn(x => x.Alias, relationTypeAliases);
- }
+ var subQuery = Sql().Select(x => x.Id)
+ .From()
+ .InnerJoin().On(x => x.RelationType, x => x.Id)
+ .Where(x => x.ParentId == parentId);
- Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery));
+ if (relationTypeAliases.Length > 0)
+ {
+ subQuery.WhereIn(x => x.Alias, relationTypeAliases);
+ }
+
+ Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery));
+
+ }
+ else
+ {
+ if (relationTypeAliases.Length > 0)
+ {
+ var template = SqlContext.Templates.Get(
+ Constants.SqlTemplates.RelationRepository.DeleteByParentIn,
+ tsql => Sql().Delete()
+ .From()
+ .InnerJoin().On(x => x.RelationType, x => x.Id)
+ .Where(x => x.ParentId == SqlTemplate.Arg("parentId"))
+ .WhereIn(x => x.Alias, SqlTemplate.ArgIn("relationTypeAliases")));
+
+ var sql = template.Sql(parentId, relationTypeAliases);
+
+ Database.Execute(sql);
+ }
+ else
+ {
+ var template = SqlContext.Templates.Get(
+ Constants.SqlTemplates.RelationRepository.DeleteByParentAll,
+ tsql => Sql().Delete()
+ .From()
+ .InnerJoin().On(x => x.RelationType, x => x.Id)
+ .Where(x => x.ParentId == SqlTemplate.Arg("parentId")));
+
+ var sql = template.Sql(parentId);
+
+ Database.Execute(sql);
+ }
+ }
}
///
diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs
index 37c47da50d..74aba854e3 100644
--- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs
@@ -2,14 +2,13 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
-using System.Data.SqlClient;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
using NPoco;
using StackExchange.Profiling;
+using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence.FaultHandling;
-using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
@@ -25,6 +24,7 @@ namespace Umbraco.Core.Persistence
{
private readonly ILogger _logger;
private readonly IBulkSqlInsertProvider _bulkSqlInsertProvider;
+ private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy;
private readonly Guid _instanceGuid = Guid.NewGuid();
@@ -39,13 +39,14 @@ namespace Umbraco.Core.Persistence
/// Used by UmbracoDatabaseFactory to create databases.
/// Also used by DatabaseBuilder for creating databases and installing/upgrading.
///
- public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, IBulkSqlInsertProvider bulkSqlInsertProvider, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
+ public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, IBulkSqlInsertProvider bulkSqlInsertProvider, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
: base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
{
SqlContext = sqlContext;
_logger = logger;
_bulkSqlInsertProvider = bulkSqlInsertProvider;
+ _databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
_connectionRetryPolicy = connectionRetryPolicy;
_commandRetryPolicy = commandRetryPolicy;
@@ -177,7 +178,20 @@ namespace Umbraco.Core.Persistence
}
+ ///
+ /// Returns the for the database
+ ///
+ public DatabaseSchemaResult ValidateSchema()
+ {
+ var dbSchema = _databaseSchemaCreatorFactory.Create(this);
+ var databaseSchemaValidationResult = dbSchema.ValidateSchema();
+ return databaseSchemaValidationResult;
+ }
+ ///
+ /// Returns true if Umbraco database tables are detected to be installed
+ ///
+ public bool IsUmbracoInstalled() => ValidateSchema().DetermineHasInstalledVersion();
#endregion
diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs
index 714721ef2c..204f904f68 100644
--- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs
+++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs
@@ -1,6 +1,6 @@
using System;
+using System.Linq;
using Umbraco.Core.Persistence.Dtos;
-using Umbraco.Core.Runtime;
namespace Umbraco.Core.Persistence
{
@@ -27,5 +27,33 @@ namespace Umbraco.Core.Persistence
.Where(x => x.Key == key);
return database.FirstOrDefault(sql)?.Value;
}
+
+
+ ///
+ /// Returns true if the database contains the specified table
+ ///
+ ///
+ ///
+ ///
+ public static bool HasTable(this IUmbracoDatabase database, string tableName)
+ {
+ try
+ {
+ return database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any(table => table.InvariantEquals(tableName));
+ }
+ catch (Exception)
+ {
+ return false; // will occur if the database cannot connect
+ }
+ }
+
+ ///
+ /// Returns true if the database contains no tables
+ ///
+ ///
+ ///
+ public static bool IsDatabaseEmpty(this IUmbracoDatabase database)
+ => database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any() == false;
+
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs
index 5c3c984677..69c8975ed5 100644
--- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
using NPoco;
using NPoco.FluentMappings;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence.FaultHandling;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -27,6 +28,7 @@ namespace Umbraco.Core.Persistence
public class UmbracoDatabaseFactory : DisposableObjectSlim, IUmbracoDatabaseFactory
{
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
+ private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
private readonly GlobalSettings _globalSettings;
private readonly Lazy _mappers;
private readonly ILogger _logger;
@@ -70,8 +72,8 @@ namespace Umbraco.Core.Persistence
/// Initializes a new instance of the .
///
/// Used by core runtime.
- public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, IOptions globalSettings, IOptions connectionStrings, Lazy mappers,IDbProviderFactoryCreator dbProviderFactoryCreator)
- : this(logger, loggerFactory, globalSettings.Value, connectionStrings.Value, mappers, dbProviderFactoryCreator)
+ public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, IOptions globalSettings, IOptions connectionStrings, Lazy mappers,IDbProviderFactoryCreator dbProviderFactoryCreator, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
+ : this(logger, loggerFactory, globalSettings.Value, connectionStrings.Value, mappers, dbProviderFactoryCreator, databaseSchemaCreatorFactory)
{
}
@@ -80,12 +82,13 @@ namespace Umbraco.Core.Persistence
/// Initializes a new instance of the .
///
/// Used by the other ctor and in tests.
- public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
+ public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
_globalSettings = globalSettings;
_mappers = mappers ?? throw new ArgumentNullException(nameof(mappers));
_dbProviderFactoryCreator = dbProviderFactoryCreator ?? throw new ArgumentNullException(nameof(dbProviderFactoryCreator));
+ _databaseSchemaCreatorFactory = databaseSchemaCreatorFactory ?? throw new ArgumentNullException(nameof(databaseSchemaCreatorFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_loggerFactory = loggerFactory;
@@ -114,12 +117,13 @@ namespace Umbraco.Core.Persistence
/// Initializes a new instance of the .
///
/// Used in tests.
- public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, string connectionString, string providerName, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
+ public UmbracoDatabaseFactory(ILogger logger, ILoggerFactory loggerFactory, string connectionString, string providerName, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
_mappers = mappers ?? throw new ArgumentNullException(nameof(mappers));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_loggerFactory = loggerFactory;
_dbProviderFactoryCreator = dbProviderFactoryCreator ?? throw new ArgumentNullException(nameof(dbProviderFactoryCreator));
+ _databaseSchemaCreatorFactory = databaseSchemaCreatorFactory ?? throw new ArgumentNullException(nameof(databaseSchemaCreatorFactory));
if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName))
{
@@ -312,7 +316,7 @@ namespace Umbraco.Core.Persistence
// method used by NPoco's UmbracoDatabaseFactory to actually create the database instance
private UmbracoDatabase CreateDatabaseInstance()
{
- return new UmbracoDatabase(ConnectionString, SqlContext, DbProviderFactory, _loggerFactory.CreateLogger(), _bulkSqlInsertProvider, _connectionRetryPolicy, _commandRetryPolicy);
+ return new UmbracoDatabase(ConnectionString, SqlContext, DbProviderFactory, _loggerFactory.CreateLogger(), _bulkSqlInsertProvider, _databaseSchemaCreatorFactory, _connectionRetryPolicy, _commandRetryPolicy);
}
protected override void DisposeResources()
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index a05c1a7f98..de0be4ca25 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -78,6 +78,7 @@ namespace Umbraco.Infrastructure.Runtime
AppDomain.CurrentDomain.SetData("DataDirectory", _hostingEnvironment?.MapPathContentRoot(Core.Constants.SystemDirectories.Data));
+ DoUnattendedInstall();
DetermineRuntimeLevel();
if (State.Level <= RuntimeLevel.BootFailed)
@@ -100,6 +101,11 @@ namespace Umbraco.Infrastructure.Runtime
_components.Initialize();
}
+ private void DoUnattendedInstall()
+ {
+ State.DoUnattendedInstall();
+ }
+
public async Task StopAsync(CancellationToken cancellationToken)
{
_components.Terminate();
diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
index c73a817327..a150afa095 100644
--- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
@@ -10,10 +10,12 @@ using Microsoft.Extensions.Logging;
using NPoco;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
+using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
+using MapperCollection = Umbraco.Core.Persistence.Mappers.MapperCollection;
namespace Umbraco.Core.Runtime
{
@@ -31,19 +33,21 @@ namespace Umbraco.Core.Runtime
private readonly UmbracoDatabaseFactory _dbFactory;
private bool _errorDuringAcquiring;
private object _locker = new object();
+ private bool _hasTable = false;
- public SqlMainDomLock(ILogger logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment)
+ public SqlMainDomLock(ILogger logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment, DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
// unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer
_lockId = Guid.NewGuid().ToString();
_logger = logger;
- _hostingEnvironment = hostingEnvironment;
+_hostingEnvironment = hostingEnvironment;
_dbFactory = new UmbracoDatabaseFactory(loggerFactory.CreateLogger(),
loggerFactory,
globalSettings,
connectionStrings,
- new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty())),
- dbProviderFactoryCreator);
+ new Lazy(() => new MapperCollection(Enumerable.Empty())),
+ dbProviderFactoryCreator,
+ databaseSchemaCreatorFactory);
MainDomKey = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId(_hostingEnvironment)).GenerateHash();
}
@@ -52,7 +56,7 @@ namespace Umbraco.Core.Runtime
{
if (!_dbFactory.Configured)
{
- // if we aren't configured, then we're in an install state, in which case we have no choice but to assume we can acquire
+ // if we aren't configured then we're in an install state, in which case we have no choice but to assume we can acquire
return true;
}
@@ -72,6 +76,14 @@ namespace Umbraco.Core.Runtime
try
{
db = _dbFactory.CreateDatabase();
+
+ _hasTable = db.HasTable(Constants.DatabaseSchema.Tables.KeyValue);
+ if (!_hasTable)
+ {
+ // the Db does not contain the required table, we must be in an install state we have no choice but to assume we can acquire
+ return true;
+ }
+
db.BeginTransaction(IsolationLevel.ReadCommitted);
try
@@ -176,11 +188,24 @@ namespace Umbraco.Core.Runtime
_logger.LogDebug("Task canceled, exiting loop");
return;
}
-
IUmbracoDatabase db = null;
+
try
{
db = _dbFactory.CreateDatabase();
+
+ if (!_hasTable)
+ {
+ // re-check if its still false, we don't want to re-query once we know its there since this
+ // loop needs to use minimal resources
+ _hasTable = db.HasTable(Constants.DatabaseSchema.Tables.KeyValue);
+ if (!_hasTable)
+ {
+ // the Db does not contain the required table, we just keep looping since we can't query the db
+ continue;
+ }
+ }
+
db.BeginTransaction(IsolationLevel.ReadCommitted);
// get a read lock
_sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);
@@ -414,7 +439,7 @@ namespace Umbraco.Core.Runtime
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
- if (_dbFactory.Configured)
+ if (_dbFactory.Configured && _hasTable)
{
IUmbracoDatabase db = null;
try
diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs
index 505cf045c3..8b5e0abdc0 100644
--- a/src/Umbraco.Infrastructure/RuntimeState.cs
+++ b/src/Umbraco.Infrastructure/RuntimeState.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Exceptions;
+using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Migrations.Upgrade;
using Umbraco.Core.Persistence;
@@ -20,6 +21,7 @@ namespace Umbraco.Core
private readonly IUmbracoVersion _umbracoVersion;
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly ILogger _logger;
+ private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
///
/// The initial
@@ -33,12 +35,18 @@ namespace Umbraco.Core
///
/// Initializes a new instance of the class.
///
- public RuntimeState(IOptions globalSettings, IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger logger)
+ public RuntimeState(
+ IOptions globalSettings,
+ IUmbracoVersion umbracoVersion,
+ IUmbracoDatabaseFactory databaseFactory,
+ ILogger logger,
+ DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
_globalSettings = globalSettings.Value;
_umbracoVersion = umbracoVersion;
_databaseFactory = databaseFactory;
_logger = logger;
+ _databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
}
@@ -79,9 +87,119 @@ namespace Umbraco.Core
return;
}
- // else, keep going,
- // anything other than install wants a database - see if we can connect
- // (since this is an already existing database, assume localdb is ready)
+ // Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
+
+ switch (GetUmbracoDatabaseState(_databaseFactory))
+ {
+ case UmbracoDatabaseState.CannotConnect:
+ {
+ // cannot connect to configured database, this is bad, fail
+ _logger.LogDebug("Could not connect to database.");
+
+ if (_globalSettings.InstallMissingDatabase)
+ {
+ // ok to install on a configured but missing database
+ Level = RuntimeLevel.Install;
+ Reason = RuntimeLevelReason.InstallMissingDatabase;
+ return;
+ }
+
+ // else it is bad enough that we want to throw
+ Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase;
+ throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
+ }
+ case UmbracoDatabaseState.NotInstalled:
+ {
+ // ok to install on an empty database
+ Level = RuntimeLevel.Install;
+ Reason = RuntimeLevelReason.InstallEmptyDatabase;
+ return;
+ }
+ case UmbracoDatabaseState.NeedsUpgrade:
+ {
+ // the db version does not match... but we do have a migration table
+ // so, at least one valid table, so we quite probably are installed & need to upgrade
+
+ // although the files version matches the code version, the database version does not
+ // which means the local files have been upgraded but not the database - need to upgrade
+ _logger.LogDebug("Has not reached the final upgrade step, need to upgrade Umbraco.");
+ Level = RuntimeLevel.Upgrade;
+ Reason = RuntimeLevelReason.UpgradeMigrations;
+ }
+ break;
+ case UmbracoDatabaseState.Ok:
+ default:
+ {
+ // if we already know we want to upgrade, exit here
+ if (Level == RuntimeLevel.Upgrade)
+ return;
+
+ // the database version matches the code & files version, all clear, can run
+ Level = RuntimeLevel.Run;
+ Reason = RuntimeLevelReason.Run;
+ }
+ break;
+ }
+ }
+
+ private enum UmbracoDatabaseState
+ {
+ Ok,
+ CannotConnect,
+ NotInstalled,
+ NeedsUpgrade
+ }
+
+ private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory)
+ {
+ try
+ {
+ if (!TryDbConnect(databaseFactory))
+ {
+ return UmbracoDatabaseState.CannotConnect;
+ }
+
+ // no scope, no service - just directly accessing the database
+ using (var database = databaseFactory.CreateDatabase())
+ {
+ if (!database.IsUmbracoInstalled())
+ {
+ return UmbracoDatabaseState.NotInstalled;
+ }
+
+ if (DoesUmbracoRequireUpgrade(database))
+ {
+ return UmbracoDatabaseState.NeedsUpgrade;
+ }
+ }
+
+ return UmbracoDatabaseState.Ok;
+ }
+ catch (Exception e)
+ {
+ // can connect to the database so cannot check the upgrade state... oops
+ _logger.LogWarning(e, "Could not check the upgrade state.");
+
+ // else it is bad enough that we want to throw
+ Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
+ throw new BootFailedException("Could not check the upgrade state.", e);
+ }
+ }
+
+ public void Configure(RuntimeLevel level, RuntimeLevelReason reason)
+ {
+ Level = level;
+ Reason = reason;
+ }
+
+ public void DoUnattendedInstall()
+ {
+ // unattended install is not enabled
+ if (_globalSettings.InstallUnattended == false) return;
+
+ // no connection string set
+ if (_databaseFactory.Configured == false) return;
+
var connect = false;
var tries = _globalSettings.InstallMissingDatabase ? 2 : 5;
for (var i = 0;;)
@@ -92,79 +210,37 @@ namespace Umbraco.Core
Thread.Sleep(1000);
}
- if (connect == false)
- {
- // cannot connect to configured database, this is bad, fail
- _logger.LogDebug("Could not connect to database.");
+ // could not connect to the database
+ if (connect == false) return;
- if (_globalSettings.InstallMissingDatabase)
+ using (var database = _databaseFactory.CreateDatabase())
+ {
+ var hasUmbracoTables = database.IsUmbracoInstalled();
+
+ // database has umbraco tables, assume Umbraco is already installed
+ if (hasUmbracoTables) return;
+
+ // all conditions fulfilled, do the install
+ _logger.LogInformation("Starting unattended install.");
+
+ try
{
- // ok to install on a configured but missing database
- Level = RuntimeLevel.Install;
- Reason = RuntimeLevelReason.InstallMissingDatabase;
- return;
+ database.BeginTransaction();
+ var creator = _databaseSchemaCreatorFactory.Create(database);
+ creator.InitializeDatabaseSchema();
+ database.CompleteTransaction();
+ _logger.LogInformation("Unattended install completed.");
}
-
- // else it is bad enough that we want to throw
- Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase;
- throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
- }
-
- // if we already know we want to upgrade,
- // still run EnsureUmbracoUpgradeState to get the states
- // (v7 will just get a null state, that's ok)
-
- // else
- // look for a matching migration entry - bypassing services entirely - they are not 'up' yet
- bool noUpgrade;
- try
- {
- noUpgrade = EnsureUmbracoUpgradeState(_databaseFactory, _logger);
- }
- catch (Exception e)
- {
- // can connect to the database but cannot check the upgrade state... oops
- _logger.LogWarning(e, "Could not check the upgrade state.");
-
- if (_globalSettings.InstallEmptyDatabase)
+ catch (Exception ex)
{
- // ok to install on an empty database
- Level = RuntimeLevel.Install;
- Reason = RuntimeLevelReason.InstallEmptyDatabase;
- return;
+ _logger.LogInformation(ex, "Error during unattended install.");
+ database.AbortTransaction();
+
+ throw new UnattendedInstallException(
+ "The database configuration failed with the following message: " + ex.Message
+ + "\n Please check log file for additional information (can be found in '/App_Data/Logs/')");
}
-
- // else it is bad enough that we want to throw
- Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
- throw new BootFailedException("Could not check the upgrade state.", e);
}
-
- // if we already know we want to upgrade, exit here
- if (Level == RuntimeLevel.Upgrade)
- return;
-
- if (noUpgrade)
- {
- // the database version matches the code & files version, all clear, can run
- Level = RuntimeLevel.Run;
- Reason = RuntimeLevelReason.Run;
- return;
- }
-
- // the db version does not match... but we do have a migration table
- // so, at least one valid table, so we quite probably are installed & need to upgrade
-
- // although the files version matches the code version, the database version does not
- // which means the local files have been upgraded but not the database - need to upgrade
- _logger.LogDebug("Has not reached the final upgrade step, need to upgrade Umbraco.");
- Level = RuntimeLevel.Upgrade;
- Reason = RuntimeLevelReason.UpgradeMigrations;
- }
-
- public void Configure(RuntimeLevel level, RuntimeLevelReason reason)
- {
- Level = level;
- Reason = reason;
}
private bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
@@ -183,5 +259,36 @@ namespace Umbraco.Core
return CurrentMigrationState == FinalMigrationState;
}
+ private bool DoesUmbracoRequireUpgrade(IUmbracoDatabase database)
+ {
+ var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion));
+ var stateValueKey = upgrader.StateValueKey;
+
+ CurrentMigrationState = database.GetFromKeyValueTable(stateValueKey);
+ FinalMigrationState = upgrader.Plan.FinalState;
+
+ _logger.LogDebug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "");
+
+ return CurrentMigrationState != FinalMigrationState;
+ }
+
+ private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory)
+ {
+ // anything other than install wants a database - see if we can connect
+ // (since this is an already existing database, assume localdb is ready)
+ bool canConnect;
+ var tries = _globalSettings.InstallMissingDatabase ? 2 : 5;
+ for (var i = 0; ;)
+ {
+ canConnect = databaseFactory.CanConnect;
+ if (canConnect || ++i == tries) break;
+ _logger.LogDebug("Could not immediately connect to database, trying again.");
+ Thread.Sleep(1000);
+ }
+
+ return canConnect;
+ }
+
+
}
}
diff --git a/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js b/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js
index ea9e4620c8..d7ede6dfa6 100644
--- a/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js
+++ b/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js
@@ -35,6 +35,8 @@
'lib/umbraco/NamespaceManager.js',
'lib/umbraco/LegacySpeechBubble.js',
+ 'js/utilities.js',
+
'js/app.js',
'js/umbraco.resources.js',
@@ -44,5 +46,5 @@
'js/umbraco.interceptors.js',
'js/umbraco.controllers.js',
'js/routes.js',
- 'js/init.js'
+ 'js/init.js'
]
diff --git a/src/Umbraco.Infrastructure/WebAssets/PreviewInitialize.js b/src/Umbraco.Infrastructure/WebAssets/PreviewInitialize.js
index 45ee39eefc..c19f99c02a 100644
--- a/src/Umbraco.Infrastructure/WebAssets/PreviewInitialize.js
+++ b/src/Umbraco.Infrastructure/WebAssets/PreviewInitialize.js
@@ -3,6 +3,7 @@
'lib/angular/angular.js',
'lib/underscore/underscore-min.js',
'lib/umbraco/Extensions.js',
+ 'js/utilities.js',
'js/app.js',
'js/umbraco.resources.js',
'js/umbraco.services.js',
diff --git a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs
index 5c9459bf47..2d8a93e772 100644
--- a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs
@@ -23,7 +23,7 @@ namespace Umbraco.Tests.Common.Builders
public override IConfigurationEditor Build()
{
- IDictionary defaultConfiguration = _defaultConfiguration ?? new Dictionary();
+ IDictionary defaultConfiguration = _defaultConfiguration ?? new Dictionary();
return new ConfigurationEditor()
{
diff --git a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs
index bd7e3b5b3f..715b504d71 100644
--- a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs
@@ -220,7 +220,6 @@ namespace Umbraco.Tests.Common.Builders
.WithName(name)
.WithParent(parent);
-
if (!(culture is null))
{
builder = builder.WithCultureName(culture, name);
diff --git a/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs
index f9ad509be4..a9ff520228 100644
--- a/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs
@@ -13,7 +13,8 @@ namespace Umbraco.Tests.Common.Builders
private string _alias;
private object _value;
- public ContentPropertyBasicBuilder(TParent parentBuilder) : base(parentBuilder)
+ public ContentPropertyBasicBuilder(TParent parentBuilder)
+ : base(parentBuilder)
{
}
diff --git a/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs
index b1e3c494f1..3e7c886623 100644
--- a/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Common.Builders
{
}
- public ContentTypeSortBuilder(ContentTypeBuilder parentBuilder) : base(parentBuilder)
+ public ContentTypeSortBuilder(ContentTypeBuilder parentBuilder)
+ : base(parentBuilder)
{
}
diff --git a/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs
index a600d2d962..b4db018322 100644
--- a/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs
@@ -48,8 +48,7 @@ namespace Umbraco.Tests.Common.Builders
Mock.Of(),
Mock.Of(),
Mock.Of(),
- Mock.Of()
- )
+ Mock.Of())
{
DefaultConfiguration = defaultConfiguration,
ExplicitConfigurationEditor = explicitConfigurationEditor,
diff --git a/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs
index 61f6c3df78..70a8e2c706 100644
--- a/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs
@@ -58,8 +58,7 @@ namespace Umbraco.Tests.Common.Builders
Mock.Of(),
Mock.Of(),
Mock.Of(),
- Mock.Of()
- )
+ Mock.Of())
{
Configuration = configuration,
View = view,
diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs
index 5b844869f0..8ab04bcc3f 100644
--- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
namespace Umbraco.Tests.Common.Builders.Interfaces
{
public interface IWithLoginBuilder
diff --git a/src/Umbraco.Tests.Common/Extensions/ContentBaseExtensions.cs b/src/Umbraco.Tests.Common/Extensions/ContentBaseExtensions.cs
index c442efe334..148bd467db 100644
--- a/src/Umbraco.Tests.Common/Extensions/ContentBaseExtensions.cs
+++ b/src/Umbraco.Tests.Common/Extensions/ContentBaseExtensions.cs
@@ -1,4 +1,8 @@
-using System;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System;
+using System.Reflection;
using Umbraco.Core.Models;
namespace Umbraco.Tests.Testing
@@ -12,16 +16,21 @@ namespace Umbraco.Tests.Testing
public static void PropertyValues(this IContentBase content, object value, string culture = null, string segment = null)
{
if (value == null)
+ {
throw new Exception("No properties has been passed in");
+ }
- var propertyInfos = value.GetType().GetProperties();
- foreach (var propertyInfo in propertyInfos)
+ PropertyInfo[] propertyInfos = value.GetType().GetProperties();
+ foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (!content.Properties.TryGetValue(propertyInfo.Name, out var property))
+ {
throw new Exception($"The property alias {propertyInfo.Name} is not valid, because no PropertyType with this alias exists");
+ }
property.SetValue(propertyInfo.GetValue(value, null), culture, segment);
- //Update item with newly added value
+
+ // Update item with newly added value
content.Properties.Add(property);
}
}
diff --git a/src/Umbraco.Tests.Common/Published/PublishedSnapshotTestObjects.cs b/src/Umbraco.Tests.Common/Published/PublishedSnapshotTestObjects.cs
index 1c618380a0..2fb6c305fb 100644
--- a/src/Umbraco.Tests.Common/Published/PublishedSnapshotTestObjects.cs
+++ b/src/Umbraco.Tests.Common/Published/PublishedSnapshotTestObjects.cs
@@ -1,4 +1,7 @@
-using System.Collections.Generic;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System.Collections.Generic;
using Moq;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
@@ -7,13 +10,13 @@ namespace Umbraco.Tests.Published
{
public class PublishedSnapshotTestObjects
{
-
[PublishedModel("element1")]
public class TestElementModel1 : PublishedElementModel
{
public TestElementModel1(IPublishedElement content, IPublishedValueFallback fallback)
: base(content)
- { }
+ {
+ }
public string Prop1 => this.Value(Mock.Of(), "prop1");
}
@@ -23,7 +26,8 @@ namespace Umbraco.Tests.Published
{
public TestElementModel2(IPublishedElement content, IPublishedValueFallback fallback)
: base(content)
- { }
+ {
+ }
public IEnumerable Prop2 => this.Value>(Mock.Of(), "prop2");
}
@@ -33,7 +37,8 @@ namespace Umbraco.Tests.Published
{
public TestContentModel1(IPublishedContent content, IPublishedValueFallback fallback)
: base(content)
- { }
+ {
+ }
public string Prop1 => this.Value(Mock.Of(), "prop1");
}
@@ -43,10 +48,10 @@ namespace Umbraco.Tests.Published
{
public TestContentModel2(IPublishedContent content, IPublishedValueFallback fallback)
: base(content)
- { }
+ {
+ }
public IEnumerable Prop2 => this.Value>(Mock.Of(), "prop2");
}
-
}
}
diff --git a/src/Umbraco.Tests.Common/TestClone.cs b/src/Umbraco.Tests.Common/TestClone.cs
index 5fd0aa30e2..4aa957e376 100644
--- a/src/Umbraco.Tests.Common/TestClone.cs
+++ b/src/Umbraco.Tests.Common/TestClone.cs
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System;
using Umbraco.Core.Models;
namespace Umbraco.Tests.Common
@@ -11,18 +14,13 @@ namespace Umbraco.Tests.Common
IsClone = true;
}
- public TestClone()
- {
- Id = Guid.NewGuid();
- }
+ public TestClone() => Id = Guid.NewGuid();
public Guid Id { get; }
+
public bool IsClone { get; }
- public object DeepClone()
- {
- return new TestClone(Id);
- }
+ public object DeepClone() => new TestClone(Id);
///
/// Indicates whether the current object is equal to another object of the same type.
@@ -33,8 +31,16 @@ namespace Umbraco.Tests.Common
/// An object to compare with this object.
public bool Equals(TestClone other)
{
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
+ if (other is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
return Id.Equals(other.Id);
}
@@ -47,9 +53,21 @@ namespace Umbraco.Tests.Common
/// The object to compare with the current object.
public override bool Equals(object obj)
{
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
+ if (obj is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != GetType())
+ {
+ return false;
+ }
+
return Equals((TestClone)obj);
}
@@ -59,19 +77,10 @@ namespace Umbraco.Tests.Common
///
/// A hash code for the current object.
///
- public override int GetHashCode()
- {
- return Id.GetHashCode();
- }
+ public override int GetHashCode() => Id.GetHashCode();
- public static bool operator ==(TestClone left, TestClone right)
- {
- return Equals(left, right);
- }
+ public static bool operator ==(TestClone left, TestClone right) => Equals(left, right);
- public static bool operator !=(TestClone left, TestClone right)
- {
- return Equals(left, right) == false;
- }
+ public static bool operator !=(TestClone left, TestClone right) => Equals(left, right) == false;
}
}
diff --git a/src/Umbraco.Tests.Common/TestDefaultCultureAccessor.cs b/src/Umbraco.Tests.Common/TestDefaultCultureAccessor.cs
index e1bec33c94..77b3793f26 100644
--- a/src/Umbraco.Tests.Common/TestDefaultCultureAccessor.cs
+++ b/src/Umbraco.Tests.Common/TestDefaultCultureAccessor.cs
@@ -1,4 +1,7 @@
-using Umbraco.Web.PublishedCache;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using Umbraco.Web.PublishedCache;
namespace Umbraco.Tests.Common
{
diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs
index ed3e28ee21..b7ad1e7f52 100644
--- a/src/Umbraco.Tests.Common/TestHelperBase.cs
+++ b/src/Umbraco.Tests.Common/TestHelperBase.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
using System;
using System.IO;
using System.Reflection;
@@ -10,21 +13,18 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
-using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Diagnostics;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Net;
using Umbraco.Core.Persistence;
using Umbraco.Core.Serialization;
using Umbraco.Core.Strings;
+using Umbraco.Net;
+using Umbraco.Tests.Common.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.Routing;
-using Umbraco.Tests.Common.Builders;
-using System.Runtime.InteropServices;
-using Umbraco.Tests.Common.TestHelpers;
namespace Umbraco.Tests.Common
{
@@ -46,16 +46,14 @@ namespace Umbraco.Tests.Common
public ITypeFinder GetTypeFinder() => _typeFinder;
- public TypeLoader GetMockedTypeLoader()
- {
- return new TypeLoader(Mock.Of(), Mock.Of(), new DirectoryInfo(GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of());
- }
+ public TypeLoader GetMockedTypeLoader() =>
+ new TypeLoader(Mock.Of(), Mock.Of(), new DirectoryInfo(GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of());
- // public Configs GetConfigs() => GetConfigsFactory().Create();
+ //// public Configs GetConfigs() => GetConfigsFactory().Create();
public abstract IBackOfficeInfo GetBackOfficeInfo();
- //public IConfigsFactory GetConfigsFactory() => new ConfigsFactory();
+ //// public IConfigsFactory GetConfigsFactory() => new ConfigsFactory();
///
/// Gets the working directory of the test project.
@@ -64,24 +62,36 @@ namespace Umbraco.Tests.Common
{
get
{
- if (_workingDir != null) return _workingDir;
+ if (_workingDir != null)
+ {
+ return _workingDir;
+ }
var dir = Path.Combine(Assembly.GetExecutingAssembly().GetRootDirectorySafe(), "TEMP");
if (!Directory.Exists(dir))
+ {
Directory.CreateDirectory(dir);
+ }
+
_workingDir = dir;
return _workingDir;
}
}
public IShortStringHelper ShortStringHelper { get; } = new DefaultShortStringHelper(new DefaultShortStringHelperConfig());
+
public IJsonSerializer JsonSerializer { get; } = new JsonNetSerializer();
+
public IVariationContextAccessor VariationContextAccessor { get; } = new TestVariationContextAccessor();
+
public abstract IDbProviderFactoryCreator DbProviderFactoryCreator { get; }
+
public abstract IBulkSqlInsertProvider BulkSqlInsertProvider { get; }
+
public abstract IMarchal Marchal { get; }
- public CoreDebugSettings CoreDebugSettings { get; } = new CoreDebugSettings();
+
+ public CoreDebugSettings CoreDebugSettings { get; } = new CoreDebugSettings();
public IIOHelper IOHelper
{
@@ -89,7 +99,7 @@ namespace Umbraco.Tests.Common
{
if (_ioHelper == null)
{
- var hostingEnvironment = GetHostingEnvironment();
+ IHostingEnvironment hostingEnvironment = GetHostingEnvironment();
if (TestEnvironment.IsWindows)
{
@@ -108,29 +118,35 @@ namespace Umbraco.Tests.Common
throw new NotSupportedException("Unexpected OS");
}
}
+
return _ioHelper;
}
}
public IMainDom MainDom { get; }
+
public UriUtility UriUtility
{
get
{
if (_uriUtility == null)
+ {
_uriUtility = new UriUtility(GetHostingEnvironment());
+ }
+
return _uriUtility;
}
}
+
///
/// Some test files are copied to the /bin (/bin/debug) on build, this is a utility to return their physical path based on a virtual path name
///
- ///
- ///
public virtual string MapPathForTestFiles(string relativePath)
{
if (!relativePath.StartsWith("~/"))
+ {
throw new ArgumentException("relativePath must start with '~/'", nameof(relativePath));
+ }
var codeBase = typeof(TestHelperBase).Assembly.CodeBase;
var uri = new Uri(codeBase);
@@ -142,20 +158,15 @@ namespace Umbraco.Tests.Common
public IUmbracoVersion GetUmbracoVersion() => new UmbracoVersion();
- public IServiceCollection GetRegister()
- {
- return new ServiceCollection();
- }
+ public IServiceCollection GetRegister() => new ServiceCollection();
public abstract IHostingEnvironment GetHostingEnvironment();
+
public abstract IApplicationShutdownRegistry GetHostingEnvironmentLifetime();
public abstract IIpResolver GetIpResolver();
- public IRequestCache GetRequestCache()
- {
- return new DictionaryAppCache();
- }
+ public IRequestCache GetRequestCache() => new DictionaryAppCache();
public IPublishedUrlProvider GetPublishedUrlProvider()
{
@@ -168,7 +179,7 @@ namespace Umbraco.Tests.Common
{
hostingEnv = hostingEnv ?? GetHostingEnvironment();
return new LoggingConfiguration(
- Path.Combine(hostingEnv.ApplicationPhysicalPath, "umbraco","logs"));
+ Path.Combine(hostingEnv.ApplicationPhysicalPath, "umbraco", "logs"));
}
}
}
diff --git a/src/Umbraco.Tests.Common/TestHelpers/MockedValueEditors.cs b/src/Umbraco.Tests.Common/TestHelpers/MockedValueEditors.cs
index b80a2996ca..9069bdcbf0 100644
--- a/src/Umbraco.Tests.Common/TestHelpers/MockedValueEditors.cs
+++ b/src/Umbraco.Tests.Common/TestHelpers/MockedValueEditors.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
using Moq;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Serialization;
@@ -10,7 +13,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
{
public static DataValueEditor CreateDataValueEditor(string name)
{
- var valueType = (ValueTypes.IsValue(name)) ? name : ValueTypes.String;
+ var valueType = ValueTypes.IsValue(name) ? name : ValueTypes.String;
return new DataValueEditor(
Mock.Of(),
@@ -21,9 +24,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
new DataEditorAttribute(name, name, name)
{
ValueType = valueType
- }
-
- );
+ });
}
}
}
diff --git a/src/Umbraco.Tests.Common/TestHelpers/SolidPublishedSnapshot.cs b/src/Umbraco.Tests.Common/TestHelpers/SolidPublishedSnapshot.cs
index e7f6cc4442..f14b6633ba 100644
--- a/src/Umbraco.Tests.Common/TestHelpers/SolidPublishedSnapshot.cs
+++ b/src/Umbraco.Tests.Common/TestHelpers/SolidPublishedSnapshot.cs
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
@@ -28,20 +31,19 @@ namespace Umbraco.Tests.Common.PublishedContent
public IDomainCache Domains => null;
- public IDisposable ForcedPreview(bool forcedPreview, Action callback = null)
- {
- throw new NotImplementedException();
- }
+ public IDisposable ForcedPreview(bool forcedPreview, Action callback = null) => throw new NotImplementedException();
public void Resync()
- { }
+ {
+ }
public IAppCache SnapshotCache => null;
public IAppCache ElementsCache => null;
public void Dispose()
- { }
+ {
+ }
}
public class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache
@@ -50,121 +52,56 @@ namespace Umbraco.Tests.Common.PublishedContent
public SolidPublishedContentCache()
: base(false)
- { }
-
- public void Add(SolidPublishedContent content)
{
- _content[content.Id] = content.CreateModel(Mock.Of());
}
- public void Clear()
- {
- _content.Clear();
- }
+ public void Add(SolidPublishedContent content) => _content[content.Id] = content.CreateModel(Mock.Of());
- public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null)
- {
- throw new NotImplementedException();
- }
+ public void Clear() => _content.Clear();
- public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null)
- {
- throw new NotImplementedException();
- }
+ public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null) => throw new NotImplementedException();
- public string GetRouteById(bool preview, int contentId, string culture = null)
- {
- throw new NotImplementedException();
- }
+ public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null) => throw new NotImplementedException();
- public string GetRouteById(int contentId, string culture = null)
- {
- throw new NotImplementedException();
- }
+ public string GetRouteById(bool preview, int contentId, string culture = null) => throw new NotImplementedException();
- public override IPublishedContent GetById(bool preview, int contentId)
- {
- return _content.ContainsKey(contentId) ? _content[contentId] : null;
- }
+ public string GetRouteById(int contentId, string culture = null) => throw new NotImplementedException();
- public override IPublishedContent GetById(bool preview, Guid contentId)
- {
- throw new NotImplementedException();
- }
+ public override IPublishedContent GetById(bool preview, int contentId) => _content.ContainsKey(contentId) ? _content[contentId] : null;
- public override IPublishedContent GetById(bool preview, Udi nodeId)
- => throw new NotSupportedException();
+ public override IPublishedContent GetById(bool preview, Guid contentId) => throw new NotImplementedException();
- public override bool HasById(bool preview, int contentId)
- {
- return _content.ContainsKey(contentId);
- }
+ public override IPublishedContent GetById(bool preview, Udi nodeId) => throw new NotSupportedException();
- public override IEnumerable GetAtRoot(bool preview, string culture = null)
- {
- return _content.Values.Where(x => x.Parent == null);
- }
+ public override bool HasById(bool preview, int contentId) => _content.ContainsKey(contentId);
- public override IPublishedContent GetSingleByXPath(bool preview, string xpath, Core.Xml.XPathVariable[] vars)
- {
- throw new NotImplementedException();
- }
+ public override IEnumerable GetAtRoot(bool preview, string culture = null) => _content.Values.Where(x => x.Parent == null);
- public override IPublishedContent GetSingleByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars)
- {
- throw new NotImplementedException();
- }
+ public override IPublishedContent GetSingleByXPath(bool preview, string xpath, Core.Xml.XPathVariable[] vars) => throw new NotImplementedException();
- public override IEnumerable GetByXPath(bool preview, string xpath, Core.Xml.XPathVariable[] vars)
- {
- throw new NotImplementedException();
- }
+ public override IPublishedContent GetSingleByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars) => throw new NotImplementedException();
- public override IEnumerable GetByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars)
- {
- throw new NotImplementedException();
- }
+ public override IEnumerable GetByXPath(bool preview, string xpath, Core.Xml.XPathVariable[] vars) => throw new NotImplementedException();
- public override System.Xml.XPath.XPathNavigator CreateNavigator(bool preview)
- {
- throw new NotImplementedException();
- }
+ public override IEnumerable GetByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars) => throw new NotImplementedException();
- public override System.Xml.XPath.XPathNavigator CreateNodeNavigator(int id, bool preview)
- {
- throw new NotImplementedException();
- }
+ public override System.Xml.XPath.XPathNavigator CreateNavigator(bool preview) => throw new NotImplementedException();
- public override bool HasContent(bool preview)
- {
- return _content.Count > 0;
- }
+ public override System.Xml.XPath.XPathNavigator CreateNodeNavigator(int id, bool preview) => throw new NotImplementedException();
- public override IPublishedContentType GetContentType(int id)
- {
- throw new NotImplementedException();
- }
+ public override bool HasContent(bool preview) => _content.Count > 0;
- public override IPublishedContentType GetContentType(string alias)
- {
- throw new NotImplementedException();
- }
+ public override IPublishedContentType GetContentType(int id) => throw new NotImplementedException();
- public override IPublishedContentType GetContentType(Guid key)
- {
- throw new NotImplementedException();
- }
+ public override IPublishedContentType GetContentType(string alias) => throw new NotImplementedException();
- public override IEnumerable GetByContentType(IPublishedContentType contentType)
- {
- throw new NotImplementedException();
- }
+ public override IPublishedContentType GetContentType(Guid key) => throw new NotImplementedException();
+
+ public override IEnumerable GetByContentType(IPublishedContentType contentType) => throw new NotImplementedException();
}
public class SolidPublishedContent : IPublishedContent
{
- #region Constructor
-
public SolidPublishedContent(IPublishedContentType contentType)
{
// initialize boring stuff
@@ -176,68 +113,67 @@ namespace Umbraco.Tests.Common.PublishedContent
ContentType = contentType;
}
- #endregion
-
- #region Content
-
private Dictionary _cultures;
- private Dictionary GetCultures()
- {
- return new Dictionary { { "", new PublishedCultureInfo("", Name, UrlSegment, UpdateDate) } };
- }
+ private Dictionary GetCultures() => new Dictionary { { string.Empty, new PublishedCultureInfo(string.Empty, Name, UrlSegment, UpdateDate) } };
public int Id { get; set; }
+
public Guid Key { get; set; }
+
public int? TemplateId { get; set; }
+
public int SortOrder { get; set; }
+
public string Name { get; set; }
+
public IReadOnlyDictionary Cultures => _cultures ?? (_cultures = GetCultures());
+
public string UrlSegment { get; set; }
+
public int WriterId { get; set; }
+
public int CreatorId { get; set; }
+
public string Path { get; set; }
+
public DateTime CreateDate { get; set; }
+
public DateTime UpdateDate { get; set; }
+
public Guid Version { get; set; }
+
public int Level { get; set; }
public PublishedItemType ItemType => PublishedItemType.Content;
+
public bool IsDraft(string culture = null) => false;
+
public bool IsPublished(string culture = null) => true;
- #endregion
-
- #region Tree
-
public int ParentId { get; set; }
+
public IEnumerable ChildIds { get; set; }
public IPublishedContent Parent { get; set; }
+
public IEnumerable Children { get; set; }
+
public IEnumerable ChildrenForAllCultures => Children;
- #endregion
-
- #region ContentType
-
public IPublishedContentType ContentType { get; set; }
- #endregion
-
- #region Properties
-
public IEnumerable Properties { get; set; }
- public IPublishedProperty GetProperty(string alias)
- {
- return Properties.FirstOrDefault(p => p.Alias.InvariantEquals(alias));
- }
+ public IPublishedProperty GetProperty(string alias) => Properties.FirstOrDefault(p => p.Alias.InvariantEquals(alias));
public IPublishedProperty GetProperty(string alias, bool recurse)
{
- var property = GetProperty(alias);
- if (recurse == false) return property;
+ IPublishedProperty property = GetProperty(alias);
+ if (recurse == false)
+ {
+ return property;
+ }
IPublishedContent content = this;
while (content != null && (property == null || property.HasValue() == false))
@@ -257,22 +193,28 @@ namespace Umbraco.Tests.Common.PublishedContent
return property == null || property.HasValue() == false ? null : property.GetValue();
}
}
-
- #endregion
}
public class SolidPublishedProperty : IPublishedProperty
{
public IPublishedPropertyType PropertyType { get; set; }
+
public string Alias { get; set; }
+
public object SolidSourceValue { get; set; }
+
public object SolidValue { get; set; }
+
public bool SolidHasValue { get; set; }
+
public object SolidXPathValue { get; set; }
public virtual object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue;
+
public virtual object GetValue(string culture = null, string segment = null) => SolidValue;
+
public virtual object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue;
+
public virtual bool HasValue(string culture = null, string segment = null) => SolidHasValue;
}
@@ -355,13 +297,10 @@ namespace Umbraco.Tests.Common.PublishedContent
[PublishedModel("ContentType2")]
public class ContentType2 : PublishedContentModel
{
- #region Plumbing
-
public ContentType2(IPublishedContent content, IPublishedValueFallback fallback)
: base(content)
- { }
-
- #endregion
+ {
+ }
public int Prop1 => this.Value(Mock.Of(), "prop1");
}
@@ -369,20 +308,18 @@ namespace Umbraco.Tests.Common.PublishedContent
[PublishedModel("ContentType2Sub")]
public class ContentType2Sub : ContentType2
{
- #region Plumbing
-
public ContentType2Sub(IPublishedContent content, IPublishedValueFallback fallback)
: base(content, fallback)
- { }
-
- #endregion
+ {
+ }
}
public class PublishedContentStrong1 : PublishedContentModel
{
public PublishedContentStrong1(IPublishedContent content, IPublishedValueFallback fallback)
: base(content)
- { }
+ {
+ }
public int StrongValue => this.Value(Mock.Of(), "strongValue");
}
@@ -391,7 +328,8 @@ namespace Umbraco.Tests.Common.PublishedContent
{
public PublishedContentStrong1Sub(IPublishedContent content, IPublishedValueFallback fallback)
: base(content, fallback)
- { }
+ {
+ }
public int AnotherValue => this.Value(Mock.Of(), "anotherValue");
}
@@ -400,7 +338,8 @@ namespace Umbraco.Tests.Common.PublishedContent
{
public PublishedContentStrong2(IPublishedContent content, IPublishedValueFallback fallback)
: base(content)
- { }
+ {
+ }
public int StrongValue => this.Value(Mock.Of(), "strongValue");
}
@@ -415,9 +354,18 @@ namespace Umbraco.Tests.Common.PublishedContent
var jsonSerializer = new JsonNetSerializer();
var dataTypeServiceMock = new Mock();
- var dataType = new DataType(new VoidEditor(Mock.Of(), dataTypeServiceMock.Object,
- Mock.Of(), Mock.Of(), Mock.Of(), jsonSerializer), configurationEditorJsonSerializer)
- { Id = 666 };
+ var dataType = new DataType(
+ new VoidEditor(
+ Mock.Of(),
+ dataTypeServiceMock.Object,
+ Mock.Of(),
+ Mock.Of(),
+ Mock.Of(),
+ jsonSerializer),
+ configurationEditorJsonSerializer)
+ {
+ Id = 666
+ };
dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataType.Yield);
var factory = new PublishedContentTypeFactory(Mock.Of