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

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

2
.github/BUILD.md vendored
View File

@@ -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).

2
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2020")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Umbraco.Core.Collections
{
/// <summary>
/// Allows clearing all event handlers
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class EventClearingObservableCollection<TValue> : ObservableCollection<TValue>, INotifyCollectionChanged
{
public EventClearingObservableCollection()
{
}
public EventClearingObservableCollection(List<TValue> list) : base(list)
{
}
public EventClearingObservableCollection(IEnumerable<TValue> 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; }
}
/// <summary>
/// Clears all event handlers for the <see cref="CollectionChanged"/> event
/// </summary>
public void ClearCollectionChangedEvents() => _changed = null;
}
}

View File

@@ -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
{
/// <summary>
/// An ObservableDictionary
/// </summary>
@@ -15,7 +18,7 @@ namespace Umbraco.Core.Collections
/// </remarks>
/// <typeparam name="TValue">The type of elements contained in the BindableCollection</typeparam>
/// <typeparam name="TKey">The type of the indexing key</typeparam>
public class ObservableDictionary<TKey, TValue> : ObservableCollection<TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>
public class ObservableDictionary<TKey, TValue> : ObservableCollection<TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>, INotifyCollectionChanged
{
protected Dictionary<TKey, int> Indecies { get; }
protected Func<TValue, TKey> 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; }
}
/// <summary>
/// Clears all <see cref="CollectionChanged"/> event handlers
/// </summary>
public void ClearCollectionChangedEvents() => _changed = null;
public bool ContainsKey(TKey key)
{
return Indecies.ContainsKey(key);

View File

@@ -1,5 +1,4 @@

using Umbraco.Core.DependencyInjection;
using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.Composing
{

View File

@@ -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);
}
}

View File

@@ -193,8 +193,15 @@ namespace Umbraco.Core.Configuration.Models
public bool ShowDeprecatedPropertyEditors { get; set; } = false;
/// <summary>
/// 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.
/// </summary>
public string LoginBackgroundImage { get; set; } = "assets/img/login.jpg";
/// <summary>
/// Gets or sets a value for the path to the login screen logo image.
/// </summary>
public string LoginLogoImage { get; set; } = "assets/img/application/umbraco_logo_white.svg";
}
}

View File

@@ -94,10 +94,15 @@ namespace Umbraco.Core.Configuration.Models
public bool InstallMissingDatabase { get; set; } = false;
/// <summary>
/// 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.
/// </summary>
public bool InstallEmptyDatabase { get; set; } = false;
/// <remarks>
/// <para>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 <c>Install</c> level.
/// If this option is set to <c>true</c> an unattended install will be performed and the runtime enters
/// the <c>Run</c> level.</para>
/// </remarks>
public bool InstallUnattended { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether to disable the election for a single server.
/// </summary>
@@ -112,6 +117,7 @@ namespace Umbraco.Core.Configuration.Models
/// Gets or sets a value for the main dom lock.
/// </summary>
public string MainDomLock { get; set; } = string.Empty;
public string Id { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value for the path to the no content view.

View File

@@ -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";

View File

@@ -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";
}
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Core
/// <summary>
/// Allowlist for SVG tabs.
/// </summary>
public static readonly IList<string> 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<string> 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" };
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Exceptions
{
/// <summary>
/// An exception that is thrown if an unattended installation occurs.
/// </summary>
[Serializable]
public class UnattendedInstallException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="UnattendedInstallException" /> class.
/// </summary>
public UnattendedInstallException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UnattendedInstallException" /> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public UnattendedInstallException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UnattendedInstallException" /> class with a specified error message
/// and a reference to the inner exception which is the cause of this exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The inner exception, or null.</param>
public UnattendedInstallException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UnattendedInstallException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected UnattendedInstallException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -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)
{

View File

@@ -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<object>
{
private readonly ILogger<TelemetryIdentifierStep> _logger;
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly IConfigManipulator _configManipulator;
public TelemetryIdentifierStep(ILogger<TelemetryIdentifierStep> logger, IOptions<GlobalSettings> globalSettings, IConfigManipulator configManipulator)
{
_logger = logger;
_globalSettings = globalSettings;
_configManipulator = configManipulator;
}
public override Task<InstallSetupResult> ExecuteAsync(object model)
{
// Generate GUID
var telemetrySiteIdentifier = Guid.NewGuid();
try
{
_configManipulator.SetGlobalId(telemetrySiteIdentifier.ToString());
}
catch (Exception ex)
{
_logger.LogError(ex, "Couldn't update config files with a telemetry site identifier");
}
return Task.FromResult<InstallSetupResult>(null);
}
public override bool RequiresExecution(object model)
{
// Verify that Json value is not empty string
// Try & get a value stored in appSettings.json
var backofficeIdentifierRaw = _globalSettings.Value.Id;
// No need to add Id again if already found
return string.IsNullOrEmpty(backofficeIdentifierRaw);
}
}
}

View File

@@ -98,12 +98,17 @@ namespace Umbraco.Core.Models
set
{
if (_schedule != null)
_schedule.CollectionChanged -= ScheduleCollectionChanged;
{
_schedule.ClearCollectionChangedEvents();
}
SetPropertyValueAndDetectChanges(value, ref _schedule, nameof(ContentSchedule));
if (_schedule != null)
{
_schedule.CollectionChanged += ScheduleCollectionChanged;
}
}
}
/// <summary>
/// Collection changed event handler to ensure the schedule field is set to dirty when the schedule changes
@@ -223,12 +228,18 @@ namespace Umbraco.Core.Models
}
set
{
if (_publishInfos != null) _publishInfos.CollectionChanged -= PublishNamesCollectionChanged;
if (_publishInfos != null)
{
_publishInfos.ClearCollectionChangedEvents();
}
_publishInfos = value;
if (_publishInfos != null)
{
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
}
}
}
/// <inheritdoc/>
public string GetPublishName(string culture)
@@ -321,7 +332,7 @@ namespace Umbraco.Core.Models
else
Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes);
Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add
Properties.ClearCollectionChangedEvents(); // be sure not to double add
Properties.CollectionChanged += PropertiesChanged;
}
@@ -438,7 +449,7 @@ namespace Umbraco.Core.Models
//if culture infos exist then deal with event bindings
if (clonedContent._publishInfos != null)
{
clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any
clonedContent._publishInfos.ClearCollectionChangedEvents(); //clear this event handler if any
clonedContent._publishInfos = (ContentCultureInfosCollection)_publishInfos.DeepClone(); //manually deep clone
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler
}
@@ -446,7 +457,7 @@ namespace Umbraco.Core.Models
//if properties exist then deal with event bindings
if (clonedContent._schedule != null)
{
clonedContent._schedule.CollectionChanged -= ScheduleCollectionChanged; //clear this event handler if any
clonedContent._schedule.ClearCollectionChangedEvents(); //clear this event handler if any
clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone
clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,9 +15,29 @@ namespace Umbraco.Web.Scheduling
public abstract class RecurringTaskBase : LatchedBackgroundTaskBase
{
private readonly IBackgroundTaskRunner<RecurringTaskBase> _runner;
private readonly int _periodMilliseconds;
private readonly long _periodMilliseconds;
private readonly Timer _timer;
/// <summary>
/// Initializes a new instance of the <see cref="RecurringTaskBase"/> class.
/// </summary>
/// <param name="runner">The task runner.</param>
/// <param name="delayMilliseconds">The delay.</param>
/// <param name="periodMilliseconds">The period.</param>
/// <remarks>The task will repeat itself periodically. Use this constructor to create a new task.</remarks>
protected RecurringTaskBase(IBackgroundTaskRunner<RecurringTaskBase> 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);
}
/// <summary>
/// Initializes a new instance of the <see cref="RecurringTaskBase"/> class.
/// </summary>

View File

@@ -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.
/// </param>
/// <param name="isElement">Wether the composite content types should be applicable for an element type</param>
/// <param name="isElement">Whether the composite content types should be applicable for an element type</param>
/// <returns></returns>
public static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(this IContentTypeService ctService,
IContentTypeComposition source,

View File

@@ -57,5 +57,6 @@ namespace Umbraco.Core
void Configure(RuntimeLevel level, RuntimeLevelReason reason);
void DoUnattendedInstall();
}
}

View File

@@ -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<TelemetryMarkerComponent> _logger;
private readonly IRuntimeState _runtime;
private readonly IHostingEnvironment _hostingEnvironment;
public TelemetryMarkerComponent(ILogger<TelemetryMarkerComponent> 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()
{
}
}
}

View File

@@ -1,8 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Telemetry
{
public class TelemetryMarkerComposer : ComponentComposer<TelemetryMarkerComponent>, ICoreComposer
{ }
}

View File

@@ -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();

View File

@@ -194,11 +194,12 @@ namespace Umbraco.Infrastructure.DependencyInjection
var hostingEnvironment = factory.GetRequiredService<IHostingEnvironment>();
var dbCreator = factory.GetRequiredService<IDbProviderFactoryCreator>();
var databaseSchemaCreatorFactory = factory.GetRequiredService<DatabaseSchemaCreatorFactory>();
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var loggerFactory = factory.GetRequiredService<ILoggerFactory>();
return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger<SqlMainDomLock>(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment)
? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger<SqlMainDomLock>(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment, databaseSchemaCreatorFactory)
: new MainDomSemaphoreLock(loggerFactory.CreateLogger<MainDomSemaphoreLock>(), hostingEnvironment);
});

View File

@@ -17,6 +17,7 @@ namespace Umbraco.Infrastructure.DependencyInjection
builder.Services.AddScoped<InstallSetupStep, NewInstallStep>();
builder.Services.AddScoped<InstallSetupStep, UpgradeStep>();
builder.Services.AddScoped<InstallSetupStep, FilePermissionsStep>();
builder.Services.AddScoped<InstallSetupStep, TelemetryIdentifierStep>();
builder.Services.AddScoped<InstallSetupStep, DatabaseConfigureStep>();
builder.Services.AddScoped<InstallSetupStep, DatabaseInstallStep>();
builder.Services.AddScoped<InstallSetupStep, DatabaseUpgradeStep>();

View File

@@ -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<ReportSiteTask> _logger;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IOptions<GlobalSettings> _globalSettings;
private static HttpClient s_httpClient;
public ReportSiteTask(
ILogger<ReportSiteTask> logger,
IHostingEnvironment hostingEnvironment,
IUmbracoVersion umbracoVersion)
IUmbracoVersion umbracoVersion,
IOptions<GlobalSettings> globalSettings)
: base(TimeSpan.FromDays(1), TimeSpan.FromMinutes(1))
{
_logger = logger;
_hostingEnvironment = hostingEnvironment;
_umbracoVersion = umbracoVersion;
_globalSettings = globalSettings;
s_httpClient = new HttpClient();
}
/// <summary>
/// Runs the background task to send the anynomous ID
/// Runs the background task to send the anonymous ID
/// to telemetry service
/// </summary>
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
{

View File

@@ -131,7 +131,7 @@ namespace Umbraco.Web.Install
return true;
}
return _databaseBuilder.HasSomeNonDefaultUser() == false;
return _databaseBuilder.IsUmbracoInstalled() == false;
}
}

View File

@@ -21,6 +21,7 @@ namespace Umbraco.Web.Install
a.OfType<NewInstallStep>().First(),
a.OfType<UpgradeStep>().First(),
a.OfType<FilePermissionsStep>().First(),
a.OfType<TelemetryIdentifierStep>().First(),
a.OfType<DatabaseConfigureStep>().First(),
a.OfType<DatabaseInstallStep>().First(),
a.OfType<DatabaseUpgradeStep>().First(),

View File

@@ -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.Web.Install.Models;
@@ -34,6 +35,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,
@@ -42,7 +44,8 @@ namespace Umbraco.Web.Install.InstallSteps
IOptions<SecuritySettings> securitySettings,
IOptions<ConnectionStrings> connectionStrings,
ICookieManager cookieManager,
IBackOfficeUserManager userManager)
IBackOfficeUserManager userManager,
IDbProviderFactoryCreator dbProviderFactoryCreator)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder));
@@ -51,6 +54,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<InstallSetupResult> ExecuteAsync(UserModel user)
@@ -120,26 +124,89 @@ namespace Umbraco.Web.Install.InstallSteps
{
get
{
return RequiresExecution(null)
return ShowView()
// the user UI
? "user"
//the continue install UI
// 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();
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
}
}
}

View File

@@ -29,9 +29,9 @@ namespace Umbraco.Core.Migrations.Install
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILogger<DatabaseBuilder> _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<DatabaseSchemaCreator>(), _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<DatabaseSchemaCreator>(), _loggerFactory, _umbracoVersion);
var creator = _databaseSchemaCreatorFactory.Create(database);
creator.InitializeDatabaseSchema();
message = message + "<p>Installation completed!</p>";

View File

@@ -25,10 +25,15 @@ namespace Umbraco.Core.Migrations.Install
public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger<DatabaseSchemaCreator> 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<Type> 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)

View File

@@ -0,0 +1,31 @@
using Microsoft.Extensions.Logging;
using Umbraco.Core.Configuration;
using Umbraco.Core.Persistence;
namespace Umbraco.Core.Migrations.Install
{
/// <summary>
/// Creates the initial database schema during install.
/// </summary>
public class DatabaseSchemaCreatorFactory
{
private readonly ILogger<DatabaseSchemaCreator> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IUmbracoVersion _umbracoVersion;
public DatabaseSchemaCreatorFactory(
ILogger<DatabaseSchemaCreator> logger,
ILoggerFactory loggerFactory,
IUmbracoVersion umbracoVersion)
{
_logger = logger;
_loggerFactory = loggerFactory;
_umbracoVersion = umbracoVersion;
}
public DatabaseSchemaCreator Create(IUmbracoDatabase database)
{
return new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion);
}
}
}

View File

@@ -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
/// </summary>
public class DatabaseSchemaResult
{
public DatabaseSchemaResult(ISqlSyntaxProvider sqlSyntax)
public DatabaseSchemaResult()
{
Errors = new List<Tuple<string, string>>();
TableDefinitions = new List<TableDefinition>();

View File

@@ -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<T>(IEnumerable<T> records);
bool IsUmbracoInstalled();
DatabaseSchemaResult ValidateSchema();
}
}

View File

@@ -947,7 +947,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.EnsureUniqueNodeName, tsql => tsql
.Select<NodeDto>(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name"))
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType") && x.ParentId == SqlTemplate.Arg<int>("parentId")));
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType") && x.ParentId == SqlTemplate.Arg<int>("parentId"))
);
var sql = template.Sql(NodeObjectTypeId, parentId);
var names = Database.Fetch<SimilarNodeName>(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<NodeDto>().Where<NodeDto>(x => x.ParentId == SqlTemplate.Arg<int>("parentId") && x.NodeObjectType == NodeObjectTypeId)
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql => tsql
.Select("MAX(sortOrder)")
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType") && x.ParentId == SqlTemplate.Arg<int>("parentId"))
);
return Database.ExecuteScalar<int>(template.Sql(new { parentId })) + 1;
var sql = template.Sql(NodeObjectTypeId, parentId);
var sortOrder = Database.ExecuteScalar<int?>(sql);
return (sortOrder + 1) ?? first;
}
protected virtual NodeDto GetParentNodeDto(int parentId)
{
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql =>
tsql.Select<NodeDto>().From<NodeDto>().Where<NodeDto>(x => x.NodeId == SqlTemplate.Arg<int>("parentId"))
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql => tsql
.Select<NodeDto>()
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeId == SqlTemplate.Arg<int>("parentId"))
);
return Database.Fetch<NodeDto>(template.Sql(parentId)).First();
var sql = template.Sql(parentId);
var nodeDto = Database.First<NodeDto>(sql);
return nodeDto;
}
protected virtual int GetReservedId(Guid uniqueId)
{
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql =>
tsql.Select<NodeDto>(x => x.NodeId).From<NodeDto>().Where<NodeDto>(x => x.UniqueId == SqlTemplate.Arg<Guid>("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation)
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql => tsql
.Select<NodeDto>(x => x.NodeId)
.From<NodeDto>()
.Where<NodeDto>(x => x.UniqueId == SqlTemplate.Arg<Guid>("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation)
);
var id = Database.ExecuteScalar<int?>(template.Sql(new { uniqueId = uniqueId }));
var sql = template.Sql(new { uniqueId });
var id = Database.ExecuteScalar<int?>(sql);
return id ?? 0;
}

View File

@@ -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);
}
}

View File

@@ -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<NodeDto>(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name"))
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType")));

View File

@@ -313,6 +313,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public void DeleteByParent(int parentId, params string[] relationTypeAliases)
{
if (Database.DatabaseType.IsSqlCe())
{
var subQuery = Sql().Select<RelationDto>(x => x.Id)
.From<RelationDto>()
@@ -325,6 +327,38 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
Database.Execute(Sql().Delete<RelationDto>().WhereIn<RelationDto>(x => x.Id, subQuery));
}
else
{
if (relationTypeAliases.Length > 0)
{
var template = SqlContext.Templates.Get(
Constants.SqlTemplates.RelationRepository.DeleteByParentIn,
tsql => Sql().Delete<RelationDto>()
.From<RelationDto>()
.InnerJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>(x => x.RelationType, x => x.Id)
.Where<RelationDto>(x => x.ParentId == SqlTemplate.Arg<int>("parentId"))
.WhereIn<RelationTypeDto>(x => x.Alias, SqlTemplate.ArgIn<string>("relationTypeAliases")));
var sql = template.Sql(parentId, relationTypeAliases);
Database.Execute(sql);
}
else
{
var template = SqlContext.Templates.Get(
Constants.SqlTemplates.RelationRepository.DeleteByParentAll,
tsql => Sql().Delete<RelationDto>()
.From<RelationDto>()
.InnerJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>(x => x.RelationType, x => x.Id)
.Where<RelationDto>(x => x.ParentId == SqlTemplate.Arg<int>("parentId")));
var sql = template.Sql(parentId);
Database.Execute(sql);
}
}
}
/// <summary>

View File

@@ -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<UmbracoDatabase> _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
/// <para>Used by UmbracoDatabaseFactory to create databases.</para>
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
/// </remarks>
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger<UmbracoDatabase> logger, IBulkSqlInsertProvider bulkSqlInsertProvider, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger<UmbracoDatabase> 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
}
/// <summary>
/// Returns the <see cref="DatabaseSchemaResult"/> for the database
/// </summary>
public DatabaseSchemaResult ValidateSchema()
{
var dbSchema = _databaseSchemaCreatorFactory.Create(this);
var databaseSchemaValidationResult = dbSchema.ValidateSchema();
return databaseSchemaValidationResult;
}
/// <summary>
/// Returns true if Umbraco database tables are detected to be installed
/// </summary>
public bool IsUmbracoInstalled() => ValidateSchema().DetermineHasInstalledVersion();
#endregion

View File

@@ -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<KeyValueDto>(x => x.Key == key);
return database.FirstOrDefault<KeyValueDto>(sql)?.Value;
}
/// <summary>
/// Returns true if the database contains the specified table
/// </summary>
/// <param name="database"></param>
/// <param name="tableName"></param>
/// <returns></returns>
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
}
}
/// <summary>
/// Returns true if the database contains no tables
/// </summary>
/// <param name="database"></param>
/// <returns></returns>
public static bool IsDatabaseEmpty(this IUmbracoDatabase database)
=> database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any() == false;
}
}

View File

@@ -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<IMapperCollection> _mappers;
private readonly ILogger<UmbracoDatabaseFactory> _logger;
@@ -70,8 +72,8 @@ namespace Umbraco.Core.Persistence
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used by core runtime.</remarks>
public UmbracoDatabaseFactory(ILogger<UmbracoDatabaseFactory> logger, ILoggerFactory loggerFactory, IOptions<GlobalSettings> globalSettings, IOptions<ConnectionStrings> connectionStrings, Lazy<IMapperCollection> mappers,IDbProviderFactoryCreator dbProviderFactoryCreator)
: this(logger, loggerFactory, globalSettings.Value, connectionStrings.Value, mappers, dbProviderFactoryCreator)
public UmbracoDatabaseFactory(ILogger<UmbracoDatabaseFactory> logger, ILoggerFactory loggerFactory, IOptions<GlobalSettings> globalSettings, IOptions<ConnectionStrings> connectionStrings, Lazy<IMapperCollection> 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 <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used by the other ctor and in tests.</remarks>
public UmbracoDatabaseFactory(ILogger<UmbracoDatabaseFactory> logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, Lazy<IMapperCollection> mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
public UmbracoDatabaseFactory(ILogger<UmbracoDatabaseFactory> logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, Lazy<IMapperCollection> 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 <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used in tests.</remarks>
public UmbracoDatabaseFactory(ILogger<UmbracoDatabaseFactory> logger, ILoggerFactory loggerFactory, string connectionString, string providerName, Lazy<IMapperCollection> mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
public UmbracoDatabaseFactory(ILogger<UmbracoDatabaseFactory> logger, ILoggerFactory loggerFactory, string connectionString, string providerName, Lazy<IMapperCollection> 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<UmbracoDatabase>(), _bulkSqlInsertProvider, _connectionRetryPolicy, _commandRetryPolicy);
return new UmbracoDatabase(ConnectionString, SqlContext, DbProviderFactory, _loggerFactory.CreateLogger<UmbracoDatabase>(), _bulkSqlInsertProvider, _databaseSchemaCreatorFactory, _connectionRetryPolicy, _commandRetryPolicy);
}
protected override void DisposeResources()

View File

@@ -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();

View File

@@ -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,8 +33,9 @@ namespace Umbraco.Core.Runtime
private readonly UmbracoDatabaseFactory _dbFactory;
private bool _errorDuringAcquiring;
private object _locker = new object();
private bool _hasTable = false;
public SqlMainDomLock(ILogger<SqlMainDomLock> logger, ILoggerFactory loggerFactory, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment)
public SqlMainDomLock(ILogger<SqlMainDomLock> 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();
@@ -42,8 +45,9 @@ namespace Umbraco.Core.Runtime
loggerFactory,
globalSettings,
connectionStrings,
new Lazy<IMapperCollection>(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty<BaseMapper>())),
dbProviderFactoryCreator);
new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())),
dbProviderFactoryCreator,
databaseSchemaCreatorFactory);
MainDomKey = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId(_hostingEnvironment)).GenerateHash<SHA1>();
}
@@ -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

View File

@@ -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<RuntimeState> _logger;
private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
/// <summary>
/// The initial <see cref="RuntimeState"/>
@@ -33,12 +35,18 @@ namespace Umbraco.Core
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeState"/> class.
/// </summary>
public RuntimeState(IOptions<GlobalSettings> globalSettings, IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger<RuntimeState> logger)
public RuntimeState(
IOptions<GlobalSettings> globalSettings,
IUmbracoVersion umbracoVersion,
IUmbracoDatabaseFactory databaseFactory,
ILogger<RuntimeState> logger,
DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
_globalSettings = globalSettings.Value;
_umbracoVersion = umbracoVersion;
_databaseFactory = databaseFactory;
_logger = logger;
_databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
}
@@ -79,20 +87,11 @@ 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)
var connect = false;
var tries = _globalSettings.InstallMissingDatabase ? 2 : 5;
for (var i = 0;;)
{
connect = _databaseFactory.CanConnect;
if (connect || ++i == tries) break;
_logger.LogDebug("Could not immediately connect to database, trying again.");
Thread.Sleep(1000);
}
// Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
if (connect == false)
switch (GetUmbracoDatabaseState(_databaseFactory))
{
case UmbracoDatabaseState.CannotConnect:
{
// cannot connect to configured database, this is bad, fail
_logger.LogDebug("Could not connect to database.");
@@ -109,48 +108,15 @@ namespace Umbraco.Core
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)
case UmbracoDatabaseState.NotInstalled:
{
// ok to install on an empty database
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallEmptyDatabase;
return;
}
// 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)
case UmbracoDatabaseState.NeedsUpgrade:
{
// 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
@@ -160,6 +126,65 @@ namespace Umbraco.Core
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)
{
@@ -167,6 +192,57 @@ namespace Umbraco.Core
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;;)
{
connect = _databaseFactory.CanConnect;
if (connect || ++i == tries) break;
_logger.LogDebug("Could not immediately connect to database, trying again.");
Thread.Sleep(1000);
}
// could not connect to the database
if (connect == false) return;
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
{
database.BeginTransaction();
var creator = _databaseSchemaCreatorFactory.Create(database);
creator.InitializeDatabaseSchema();
database.CompleteTransaction();
_logger.LogInformation("Unattended install completed.");
}
catch (Exception ex)
{
_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/')");
}
}
}
private bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
{
var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion));
@@ -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 ?? "<null>");
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;
}
}
}

View File

@@ -35,6 +35,8 @@
'lib/umbraco/NamespaceManager.js',
'lib/umbraco/LegacySpeechBubble.js',
'js/utilities.js',
'js/app.js',
'js/umbraco.resources.js',

View File

@@ -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',

View File

@@ -12,6 +12,7 @@ using Moq;
using NPoco;
using NPoco.DatabaseTypes;
using NPoco.Linq;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -112,6 +113,9 @@ namespace Umbraco.Tests.Testing
public int SqlCount { get; }
public int BulkInsertRecords<T>(IEnumerable<T> records) => throw new NotImplementedException();
public bool IsUmbracoInstalled() => true;
public DatabaseSchemaResult ValidateSchema() => throw new NotImplementedException();
public DbParameter CreateParameter() => throw new NotImplementedException();

View File

@@ -5,6 +5,7 @@ using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
@@ -21,19 +22,22 @@ namespace Umbraco.Tests.Integration.Testing
private readonly IOptions<ConnectionStrings> _connectionStrings;
private readonly Lazy<IMapperCollection> _mappers;
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
public TestUmbracoDatabaseFactoryProvider(
ILoggerFactory loggerFactory,
IOptions<GlobalSettings> globalSettings,
IOptions<ConnectionStrings> connectionStrings,
Lazy<IMapperCollection> mappers,
IDbProviderFactoryCreator dbProviderFactoryCreator)
IDbProviderFactoryCreator dbProviderFactoryCreator,
DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory)
{
_loggerFactory = loggerFactory;
_globalSettings = globalSettings;
_connectionStrings = connectionStrings;
_mappers = mappers;
_dbProviderFactoryCreator = dbProviderFactoryCreator;
_databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
}
public IUmbracoDatabaseFactory Create()
@@ -45,7 +49,8 @@ namespace Umbraco.Tests.Integration.Testing
_globalSettings.Value,
_connectionStrings.Value,
_mappers,
_dbProviderFactoryCreator);
_dbProviderFactoryCreator,
_databaseSchemaCreatorFactory);
}
}
}

View File

@@ -109,7 +109,7 @@ namespace Umbraco.Tests.Integration.Testing
[SetUp]
public virtual void Setup()
{
InMemoryConfiguration[Constants.Configuration.ConfigGlobal + ":" + nameof(GlobalSettings.InstallEmptyDatabase)] = "true";
InMemoryConfiguration[Constants.Configuration.ConfigGlobal + ":" + nameof(GlobalSettings.InstallUnattended)] = "true";
IHostBuilder hostBuilder = CreateHostBuilder();
IHost host = hostBuilder.Build();

View File

@@ -20,6 +20,7 @@ using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.Integration.Testing;
using Umbraco.Tests.Testing;
using Umbraco.Web.Models.ContentEditing;
using Content = Umbraco.Core.Models.Content;
namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositories
{
@@ -43,6 +44,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor
private IMediaTypeRepository MediaTypeRepository => GetRequiredService<IMediaTypeRepository>();
private IDocumentRepository DocumentRepository => GetRequiredService<IDocumentRepository>();
private IContentService ContentService => GetRequiredService<IContentService>();
private ContentTypeRepository ContentTypeRepository => (ContentTypeRepository)GetRequiredService<IContentTypeRepository>();
@@ -945,5 +947,87 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor
Assert.That(result2, Is.True);
}
}
[Test]
public void Can_Update_Variation_Of_Element_Type_Property()
{
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
ContentTypeRepository repository = ContentTypeRepository;
var contentRepository = DocumentRepository;
// Create elementType
var elementType = new ContentType(ShortStringHelper, -1)
{
Alias = "elementType",
Name = "Element type",
Description = "Element type to use as compositions",
Icon = ".sprTreeDoc3",
Thumbnail = "doc.png",
SortOrder = 1,
CreatorId = 0,
Trashed = false,
IsElement = true,
Variations = ContentVariation.Nothing
};
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = Constants.DataTypes.Textbox,
LabelOnTop = true,
Variations = ContentVariation.Nothing
});
elementType.PropertyGroups.Add(new PropertyGroup(contentCollection) {Name = "Content", SortOrder = 1});
elementType.ResetDirtyProperties(false);
elementType.SetDefaultTemplate(new Template(ShortStringHelper, "ElementType", "elementType"));
repository.Save(elementType);
// Create the basic "home" doc type that uses element type as comp
var docType = new ContentType(ShortStringHelper, -1)
{
Alias = "home",
Name = "Home",
Description = "Home containing elementType",
Icon = ".sprTreeDoc3",
Thumbnail = "doc.png",
SortOrder = 1,
CreatorId = 0,
Trashed = false,
Variations = ContentVariation.Nothing
};
var comp = new List<IContentTypeComposition>();
comp.Add(elementType);
docType.ContentTypeComposition = comp;
repository.Save(docType);
// Create "home" content
var content = new Content("Home", -1, docType){Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0};
object obj = new {title = "test title"};
content.PropertyValues(obj);
content.ResetDirtyProperties(false);
contentRepository.Save(content);
// Update variation on element type
elementType.Variations = ContentVariation.Culture;
elementType.PropertyTypes.First().Variations = ContentVariation.Culture;
repository.Save(elementType);
// Update variation on doc type
docType.Variations = ContentVariation.Culture;
repository.Save(docType);
// Re fetch renewedContent and make sure that the culture has been set.
var renewedContent = ContentService.GetById(content.Id);
var hasCulture = renewedContent.Properties["title"].Values.First().Culture != null;
Assert.That(hasCulture, Is.True);
}
}
}
}

View File

@@ -1048,7 +1048,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
Console.WriteLine(GetJson(document.Id));
AssertJsonStartsWith(
document.Id,
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
"{'pd':{'value11':[{'c':'fr','v':'v11fr'},{'c':'en','v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
composed.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture;
ContentTypeService.Save(composed);
@@ -1057,7 +1057,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
Console.WriteLine(GetJson(document.Id));
AssertJsonStartsWith(
document.Id,
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'fr','v':'v21fr'},{'c':'en','v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
"{'pd':{'value11':[{'c':'fr','v':'v11fr'},{'c':'en','v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'fr','v':'v21fr'},{'c':'en','v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
composing.Variations = ContentVariation.Nothing;
ContentTypeService.Save(composing);
@@ -1177,7 +1177,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
Console.WriteLine(GetJson(document1.Id));
AssertJsonStartsWith(
document1.Id,
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
"{'pd':{'value11':[{'c':'fr','v':'v11fr'},{'c':'en','v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
Console.WriteLine(GetJson(document2.Id));
AssertJsonStartsWith(
@@ -1191,7 +1191,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
Console.WriteLine(GetJson(document1.Id));
AssertJsonStartsWith(
document1.Id,
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'fr','v':'v21fr'},{'c':'en','v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
"{'pd':{'value11':[{'c':'fr','v':'v11fr'},{'c':'en','v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'fr','v':'v21fr'},{'c':'en','v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
Console.WriteLine(GetJson(document2.Id));
AssertJsonStartsWith(

View File

@@ -15,11 +15,13 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Scoping;
@@ -43,7 +45,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components
ILogger logger = loggerFactory.CreateLogger("GenericLogger");
var globalSettings = new GlobalSettings();
var connectionStrings = new ConnectionStrings();
var f = new UmbracoDatabaseFactory(loggerFactory.CreateLogger<UmbracoDatabaseFactory>(), loggerFactory, Options.Create(globalSettings), Options.Create(connectionStrings), new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())), TestHelper.DbProviderFactoryCreator);
var f = new UmbracoDatabaseFactory(loggerFactory.CreateLogger<UmbracoDatabaseFactory>(), loggerFactory, Options.Create(globalSettings), Options.Create(connectionStrings), new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())), TestHelper.DbProviderFactoryCreator,
new DatabaseSchemaCreatorFactory(loggerFactory.CreateLogger<DatabaseSchemaCreator>(), loggerFactory, new UmbracoVersion()));
var fs = new FileSystems(mock.Object, loggerFactory.CreateLogger<FileSystems>(), loggerFactory, IOHelper, Options.Create(globalSettings), Mock.Of<IHostingEnvironment>());
var coreDebug = new CoreDebugSettings();
IMediaFileSystem mediaFileSystem = Mock.Of<IMediaFileSystem>();

View File

@@ -30,4 +30,12 @@
<Content Include="TestHelpers\Assets\UmbracoTraceLog.UNITTEST.20181112.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="umbraco\Data\TEMP\TypesCache" />
</ItemGroup>
<ItemGroup>
<None Remove="umbraco\Data\TEMP\TypesCache\umbraco-types.DESKTOP-EU212M2.hash" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,179 @@
using System.Globalization;
using NUnit.Framework;
using Umbraco.Core.Models;
namespace Umbraco.Tests.Models
{
[TestFixture]
public class RangeTests
{
[TestCase(0, 0, "0,0")]
[TestCase(0, 1, "0,1")]
[TestCase(1, 1, "1,1")]
public void RangeInt32_ToString(int minimum, int maximum, string expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum,
Maximum = maximum
}.ToString());
}
[TestCase(0, 0.5, "0,0.5")]
[TestCase(0.5, 0.5, "0.5,0.5")]
[TestCase(0.5, 1, "0.5,1")]
public void RangeDouble_ToString(double minimum, double maximum, string expected)
{
Assert.AreEqual(expected, new Range<double>()
{
Minimum = minimum,
Maximum = maximum
}.ToString());
}
[TestCase(0, 0, "0")]
[TestCase(0, 1, "0,1")]
[TestCase(1, 1, "1")]
public void RangeInt32_ToStringFormatRange(int minimum, int maximum, string expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum,
Maximum = maximum
}.ToString("{0}", "{0},{1}", CultureInfo.InvariantCulture));
}
[TestCase(0, 0.5, "0,0.5")]
[TestCase(0.5, 0.5, "0.5")]
[TestCase(0.5, 1, "0.5,1")]
public void RangeDouble_ToStringFormatRange(double minimum, double maximum, string expected)
{
Assert.AreEqual(expected, new Range<double>()
{
Minimum = minimum,
Maximum = maximum
}.ToString("{0}", "{0},{1}", CultureInfo.InvariantCulture));
}
[TestCase(0, 0, "0,0")]
[TestCase(0, 1, "0,1")]
[TestCase(1, 1, "1,1")]
public void RangeInt32_ToStringFormat(int minimum, int maximum, string expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum,
Maximum = maximum
}.ToString("{0},{1}", CultureInfo.InvariantCulture));
}
[TestCase(0, 0.5, "0,0.5")]
[TestCase(0.5, 0.5, "0.5,0.5")]
[TestCase(0.5, 1, "0.5,1")]
public void RangeDouble_ToStringFormat(double minimum, double maximum, string expected)
{
Assert.AreEqual(expected, new Range<double>()
{
Minimum = minimum,
Maximum = maximum
}.ToString("{0},{1}", CultureInfo.InvariantCulture));
}
[TestCase(0, 0, true)]
[TestCase(0, 1, true)]
[TestCase(-1, 1, true)]
[TestCase(1, 0, false)]
[TestCase(0, -1, false)]
public void RangeInt32_IsValid(int minimum, int maximum, bool expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum,
Maximum = maximum
}.IsValid());
}
[TestCase(0, 0, 0, true)]
[TestCase(0, 1, 0, true)]
[TestCase(0, 1, 1, true)]
[TestCase(-1, 1, 0, true)]
[TestCase(0, 0, 1, false)]
[TestCase(0, 0, -1, false)]
public void RangeInt32_ContainsValue(int minimum, int maximum, int value, bool expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum,
Maximum = maximum
}.ContainsValue(value));
}
[TestCase(0, 0, 0, 0, true)]
[TestCase(0, 1, 0, 1, true)]
[TestCase(0, 1, 1, 1, false)]
[TestCase(-1, 1, 0, 0, false)]
[TestCase(0, 0, 1, 1, false)]
[TestCase(0, 0, -1, 1, true)]
public void RangeInt32_IsInsideRange(int minimum1, int maximum1, int minimum2, int maximum2, bool expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum1,
Maximum = maximum1
}.IsInsideRange(new Range<int>()
{
Minimum = minimum2,
Maximum = maximum2
}));
}
[TestCase(0, 0, 0, 0, true)]
[TestCase(0, 1, 0, 1, true)]
[TestCase(0, 1, 1, 1, true)]
[TestCase(-1, 1, 0, 0, true)]
[TestCase(0, 0, 1, 1, false)]
[TestCase(0, 0, -1, 1, false)]
public void RangeInt32_ContainsRange(int minimum1, int maximum1, int minimum2, int maximum2, bool expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum1,
Maximum = maximum1
}.ContainsRange(new Range<int>()
{
Minimum = minimum2,
Maximum = maximum2
}));
}
[TestCase(0, 0, 0, 0, true)]
[TestCase(0, 1, 0, 1, true)]
[TestCase(0, 1, 1, 1, false)]
[TestCase(0, 0, 1, 1, false)]
public void RangeInt32_Equals(int minimum1, int maximum1, int minimum2, int maximum2, bool expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum1,
Maximum = maximum1
}.Equals(new Range<int>()
{
Minimum = minimum2,
Maximum = maximum2
}));
}
[TestCase(0, 0, 0, 0, true)]
[TestCase(0, 1, 0, 1, true)]
[TestCase(0, 1, 1, 1, false)]
[TestCase(0, 0, 1, 1, false)]
public void RangeInt32_EqualsValues(int minimum1, int maximum1, int minimum2, int maximum2, bool expected)
{
Assert.AreEqual(expected, new Range<int>()
{
Minimum = minimum1,
Maximum = maximum1
}.Equals(minimum2, maximum2));
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling
{
const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco";
const string providerName = Constants.DbProviderNames.SqlServer;
var factory = new UmbracoDatabaseFactory(Mock.Of<ILogger<UmbracoDatabaseFactory>>(), NullLoggerFactory.Instance, connectionString, providerName, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
var factory = new UmbracoDatabaseFactory(Mock.Of<ILogger<UmbracoDatabaseFactory>>(), NullLoggerFactory.Instance, connectionString, providerName, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator, TestHelper.DatabaseSchemaCreatorFactory);
using (var database = factory.CreateDatabase())
{
@@ -35,7 +35,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling
{
const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco";
const string providerName = Constants.DbProviderNames.SqlServer;
var factory = new UmbracoDatabaseFactory(Mock.Of<ILogger<UmbracoDatabaseFactory>>(), NullLoggerFactory.Instance, connectionString, providerName, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
var factory = new UmbracoDatabaseFactory(Mock.Of<ILogger<UmbracoDatabaseFactory>>(), NullLoggerFactory.Instance, connectionString, providerName, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator, TestHelper.DatabaseSchemaCreatorFactory);
using (var database = factory.CreateDatabase())
{

View File

@@ -37,9 +37,11 @@ using File = System.IO.File;
using Umbraco.Tests.Common.Builders;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Mail;
using Umbraco.Core.Migrations.Install;
namespace Umbraco.Tests.TestHelpers
{
@@ -59,6 +61,7 @@ namespace Umbraco.Tests.TestHelpers
}
public override IDbProviderFactoryCreator DbProviderFactoryCreator { get; } = new UmbracoDbProviderFactoryCreator();
public DatabaseSchemaCreatorFactory DatabaseSchemaCreatorFactory { get; } = new DatabaseSchemaCreatorFactory(Mock.Of<ILogger<DatabaseSchemaCreator>>(), NullLoggerFactory.Instance, new UmbracoVersion());
public override IBulkSqlInsertProvider BulkSqlInsertProvider { get; } = new SqlCeBulkSqlInsertProvider();
@@ -99,6 +102,7 @@ namespace Umbraco.Tests.TestHelpers
public static IJsonSerializer JsonSerializer => _testHelperInternal.JsonSerializer;
public static IVariationContextAccessor VariationContextAccessor => _testHelperInternal.VariationContextAccessor;
public static IDbProviderFactoryCreator DbProviderFactoryCreator => _testHelperInternal.DbProviderFactoryCreator;
public static DatabaseSchemaCreatorFactory DatabaseSchemaCreatorFactory => _testHelperInternal.DatabaseSchemaCreatorFactory;
public static IBulkSqlInsertProvider BulkSqlInsertProvider => _testHelperInternal.BulkSqlInsertProvider;
public static IMarchal Marchal => _testHelperInternal.Marchal;
public static CoreDebugSettings CoreDebugSettings => _testHelperInternal.CoreDebugSettings;

View File

@@ -10,6 +10,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.IO;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -78,7 +79,8 @@ namespace Umbraco.Tests.TestHelpers
globalSettings,
connectionStrings,
new Lazy<IMapperCollection>(() => mappers),
TestHelper.DbProviderFactoryCreator);
TestHelper.DbProviderFactoryCreator,
new DatabaseSchemaCreatorFactory(Mock.Of<ILogger<DatabaseSchemaCreator>>(),loggerFactory, new UmbracoVersion()));
}
fileSystems ??= new FileSystems(Current.Factory, loggerFactory.CreateLogger<FileSystems>(), loggerFactory, TestHelper.IOHelper, Options.Create(globalSettings), TestHelper.GetHostingEnvironment());

View File

@@ -91,7 +91,7 @@ namespace Umbraco.Tests.TestHelpers
return TestObjects.GetDatabaseFactoryMock();
var lazyMappers = new Lazy<IMapperCollection>(f.GetRequiredService<IMapperCollection>);
var factory = new UmbracoDatabaseFactory(f.GetRequiredService<ILogger<UmbracoDatabaseFactory>>(), f.GetRequiredService<ILoggerFactory>(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator);
var factory = new UmbracoDatabaseFactory(f.GetRequiredService<ILogger<UmbracoDatabaseFactory>>(), f.GetRequiredService<ILoggerFactory>(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator, f.GetRequiredService<DatabaseSchemaCreatorFactory>());
return factory;
});
}

View File

@@ -31,6 +31,7 @@ using Umbraco.Core.Mail;
using Umbraco.Core.Manifest;
using Umbraco.Core.Mapping;
using Umbraco.Core.Media;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
@@ -487,7 +488,8 @@ namespace Umbraco.Tests.Testing
globalSettings,
connectionStrings,
new Lazy<IMapperCollection>(f.GetRequiredService<IMapperCollection>),
TestHelper.DbProviderFactoryCreator));
TestHelper.DbProviderFactoryCreator,
new DatabaseSchemaCreatorFactory(LoggerFactory.CreateLogger<DatabaseSchemaCreator>(), LoggerFactory, UmbracoVersion)));
Builder.Services.AddUnique(f => f.GetService<IUmbracoDatabaseFactory>().SqlContext);

View File

@@ -117,6 +117,12 @@
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="MiniProfiler" Version="4.0.138" />
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="NPoco" Version="4.0.3" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Owin" Version="1.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.7.0" />
@@ -145,6 +151,8 @@
<Compile Include="TestHelpers\Entities\MockedUser.cs" />
<Compile Include="TestHelpers\Entities\MockedUserGroup.cs" />
<Compile Include="UmbracoExamine\ExamineExtensions.cs" />
<Compile Include="Models\RangeTests.cs" />
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
@@ -306,9 +314,6 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Folder Include="IO\" />
</ItemGroup>
<!-- get NuGet packages directory -->
<PropertyGroup>
<NuGetPackages>$(NuGetPackageFolders.Split(';')[0])</NuGetPackages>

View File

@@ -91,7 +91,7 @@ namespace Umbraco.Web.BackOffice.Controllers
var keepOnlyKeys = new Dictionary<string, string[]>
{
{"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl", "previewHubUrl"}},
{"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "canSendRequiredEmail", "usernameIsEmail"}},
{"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "loginLogoImage", "canSendRequiredEmail", "usernameIsEmail"}},
{"application", new[] {"applicationPath", "cacheBuster"}},
{"isDebuggingEnabled", new string[] { }},
{"features", new [] {"disabledFeatures"}}
@@ -394,6 +394,7 @@ namespace Umbraco.Web.BackOffice.Controllers
{"cssPath", _hostingEnvironment.ToAbsolute(globalSettings.UmbracoCssPath).TrimEnd('/')},
{"allowPasswordReset", _securitySettings.AllowPasswordReset},
{"loginBackgroundImage", _contentSettings.LoginBackgroundImage},
{"loginLogoImage", _contentSettings.LoginLogoImage },
{"showUserInvite", EmailSender.CanSendRequiredEmail(globalSettings)},
{"canSendRequiredEmail", EmailSender.CanSendRequiredEmail(globalSettings)},
{"showAllowSegmentationForDocumentTypes", false},

View File

@@ -738,7 +738,7 @@ namespace Umbraco.Web.BackOffice.Controllers
//The default validation language will be either: The default languauge, else if the content is brand new and the default culture is
// not marked to be saved, it will be the first culture in the list marked for saving.
var defaultCulture = _allLangs.Value.Values.FirstOrDefault(x => x.IsDefault)?.CultureName;
var defaultCulture = _allLangs.Value.Values.FirstOrDefault(x => x.IsDefault)?.IsoCode;
var cultureForInvariantErrors = CultureImpact.GetCultureForInvariantErrors(
contentItem.PersistedContent,
contentItem.Variants.Where(x => x.Save).Select(x => x.Culture).ToArray(),

View File

@@ -71,7 +71,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// be looked up via the db, they need to be passed in.
/// </param>
/// <param name="contentTypeId"></param>
/// <param name="isElement">Wether the composite content types should be applicable for an element type</param>
/// <param name="isElement">Whether the composite content types should be applicable for an element type</param>
/// <returns></returns>
protected ActionResult<IEnumerable<Tuple<EntityBasic, bool>>> PerformGetAvailableCompositeContentTypes(int contentTypeId,
UmbracoObjectTypes type,

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.Extensions.Options;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration;
@@ -43,6 +44,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly ICookieManager _cookieManager;
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly ICompositeViewEngine _viewEngines;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public PreviewController(
UmbracoFeatures features,
@@ -53,7 +55,8 @@ namespace Umbraco.Web.BackOffice.Controllers
IHostingEnvironment hostingEnvironment,
ICookieManager cookieManager,
IRuntimeMinifier runtimeMinifier,
ICompositeViewEngine viewEngines)
ICompositeViewEngine viewEngines,
IUmbracoContextAccessor umbracoContextAccessor)
{
_features = features;
_globalSettings = globalSettings.Value;
@@ -64,14 +67,22 @@ namespace Umbraco.Web.BackOffice.Controllers
_cookieManager = cookieManager;
_runtimeMinifier = runtimeMinifier;
_viewEngines = viewEngines;
_umbracoContextAccessor = umbracoContextAccessor;
}
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccessWithoutApproval)]
[DisableBrowserCache]
public ActionResult Index()
public ActionResult Index(int? id = null)
{
var availableLanguages = _localizationService.GetAllLanguages();
if (id.HasValue)
{
var content = _umbracoContextAccessor.UmbracoContext.Content.GetById(true, id.Value);
if (content is null)
return NotFound();
availableLanguages = availableLanguages.Where(language => content.Cultures.ContainsKey(language.IsoCode));
}
var model = new BackOfficePreviewModel(_features, availableLanguages);
if (model.PreviewExtendedHeaderView.IsNullOrWhiteSpace() == false)
@@ -90,6 +101,7 @@ namespace Umbraco.Web.BackOffice.Controllers
return View(viewPath, model);
}
/// <summary>
/// Returns the JavaScript file for preview
/// </summary>

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -664,7 +666,16 @@ namespace Umbraco.Web.BackOffice.Controllers
var display = _umbracoMapper.Map<UserDisplay>(user);
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/operationSavedHeader"), _localizedTextService.Localize("speechBubbles/editUserSaved"));
// determine if the user has changed their own language;
var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
var userHasChangedOwnLanguage =
user.Id == currentUser.Id && currentUser.Language != user.Language;
var textToLocalise = userHasChangedOwnLanguage ? "speechBubbles/operationSavedHeaderReloadUser" : "speechBubbles/operationSavedHeader";
var culture = userHasChangedOwnLanguage
? CultureInfo.GetCultureInfo(user.Language)
: Thread.CurrentThread.CurrentUICulture;
display.AddSuccessNotification(_localizedTextService.Localize(textToLocalise, culture), _localizedTextService.Localize("speechBubbles/editUserSaved", culture));
return display;
}

View File

@@ -25,6 +25,7 @@ using Umbraco.Core.Diagnostics;
using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Security;
@@ -125,7 +126,7 @@ namespace Umbraco.Web.Common.DependencyInjection
// Add supported databases
builder.AddUmbracoSqlServerSupport();
builder.AddUmbracoSqlCeSupport();
builder.Services.AddUnique<DatabaseSchemaCreatorFactory>();
// Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now
builder.Services.AddSingleton<IDbProviderFactoryCreator>(factory => new DbProviderFactoryCreator(

View File

@@ -60,6 +60,7 @@ namespace Umbraco.Extensions
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// This must come after auth because the culture is based on the auth'd user
app.UseRequestLocalization();

View File

@@ -30,14 +30,17 @@ function dependencies() {
"./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/css.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/json.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/xml.js",
"./node_modules/ace-builds/src-min-noconflict/theme-chrome.js",
"./node_modules/ace-builds/src-min-noconflict/mode-razor.js",
"./node_modules/ace-builds/src-min-noconflict/mode-javascript.js",
"./node_modules/ace-builds/src-min-noconflict/mode-css.js",
"./node_modules/ace-builds/src-min-noconflict/mode-json.js",
"./node_modules/ace-builds/src-min-noconflict/mode-xml.js",
"./node_modules/ace-builds/src-min-noconflict/worker-javascript.js",
"./node_modules/ace-builds/src-min-noconflict/worker-css.js",
"./node_modules/ace-builds/src-min-noconflict/mode-json.js",
"./node_modules/ace-builds/src-min-noconflict/worker-json.js"
"./node_modules/ace-builds/src-min-noconflict/worker-json.js",
"./node_modules/ace-builds/src-min-noconflict/worker-xml.js"
],
"base": "./node_modules/ace-builds"
},

View File

@@ -57,6 +57,7 @@
vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin();
vm.externalLoginInfo = externalLoginInfo;
vm.resetPasswordCodeInfo = resetPasswordCodeInfo;
vm.logoImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginLogoImage;
vm.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage;
vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;

View File

@@ -126,7 +126,7 @@
// Load in ace library
assetsService.load(['lib/ace-builds/src-min-noconflict/ace.js', 'lib/ace-builds/src-min-noconflict/ext-language_tools.js'], scope).then(function () {
if (angular.isUndefined(window.ace)) {
if (Utilities.isUndefined(window.ace)) {
throw new Error('ui-ace need ace to work... (o rly?)');
} else {
// init editor

View File

@@ -5,7 +5,7 @@
@scope
@description
<strong>Added in Umbraco v. 8.8:</strong> Use this directive to render a color picker.
<strong>Added in Umbraco v. 8.10:</strong> Use this directive to render a color picker.
<h3>Markup example</h3>
<pre>

View File

@@ -30,12 +30,12 @@ Use this directive to generate color swatches to pick from.
function link(scope, el, attr, ctrl) {
// Set default to true if not defined
if (angular.isUndefined(scope.useColorClass)) {
if (Utilities.isUndefined(scope.useColorClass)) {
scope.useColorClass = false;
}
// Set default to "btn" if not defined
if (angular.isUndefined(scope.colorClassNamePrefix)) {
if (Utilities.isUndefined(scope.colorClassNamePrefix)) {
scope.colorClassNamePrefix = "btn";
}

View File

@@ -11,6 +11,7 @@
link: function (scope, element, attr, ctrl) {
var formMgr = ctrl.length > 1 ? ctrl[1] : null;
const hiddenClass = 'ng-hide';
//We can either get the form submitted status by the parent directive valFormManager
//or we can check upwards in the DOM for the css class... lets try both :)
@@ -18,17 +19,17 @@
//reset the status.
var submitted = element.closest(".show-validation").length > 0 || (formMgr && formMgr.showValidation);
if (!submitted) {
element.hide();
element[0].classList.add(hiddenClass);
}
var unsubscribe = [];
unsubscribe.push(scope.$on("formSubmitting", function (ev, args) {
element.show();
element[0].classList.remove(hiddenClass);
}));
unsubscribe.push(scope.$on("formSubmitted", function (ev, args) {
element.hide();
element[0].classList.add(hiddenClass);
}));
//no isolate scope to listen to element destroy

View File

@@ -13,7 +13,7 @@
*/
angular.module("umbraco.filters").filter('umbCmsJoinArray', function () {
return function join(array, separator, prop) {
return (!angular.isUndefined(prop) ? array.map(function (item) {
return (!Utilities.isUndefined(prop) ? array.map(function (item) {
return item[prop];
}) : array).join(separator || '');
};

View File

@@ -17,7 +17,7 @@
return collection;
}
if (angular.isUndefined(property)) {
if (Utilities.isUndefined(property)) {
return collection;
}

View File

@@ -12,7 +12,24 @@
function authResource($q, $http, umbRequestHelper, angularHelper) {
return {
/**
* @ngdoc method
* @name umbraco.resources.authResource#get2FAProviders
* @methodOf umbraco.resources.authResource
*
* @description
* Logs the Umbraco backoffice user in if the credentials are good
*
* ##usage
* <pre>
* authResource.get2FAProviders()
* .then(function(data) {
* //Do stuff ...
* });
* </pre>
* @returns {Promise} resourcePromise object
*
*/
get2FAProviders: function () {
return umbRequestHelper.resourcePromise(
@@ -23,6 +40,25 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
'Could not retrive two factor provider info');
},
/**
* @ngdoc method
* @name umbraco.resources.authResource#get2FAProviders
* @methodOf umbraco.resources.authResource
*
* @description
* Generate the two-factor authentication code for the provider and send it to the user
*
* ##usage
* <pre>
* authResource.send2FACode(provider)
* .then(function(data) {
* //Do stuff ...
* });
* </pre>
* @param {string} provider Name of the provider
* @returns {Promise} resourcePromise object
*
*/
send2FACode: function (provider) {
return umbRequestHelper.resourcePromise(
@@ -34,6 +70,26 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
'Could not send code');
},
/**
* @ngdoc method
* @name umbraco.resources.authResource#get2FAProviders
* @methodOf umbraco.resources.authResource
*
* @description
* Verify the two-factor authentication code entered by the user against the provider
*
* ##usage
* <pre>
* authResource.verify2FACode(provider, code)
* .then(function(data) {
* //Do stuff ...
* });
* </pre>
* @param {string} provider Name of the provider
* @param {string} code The two-factor authentication code
* @returns {Promise} resourcePromise object
*
*/
verify2FACode: function (provider, code) {
return umbRequestHelper.resourcePromise(

View File

@@ -7,6 +7,25 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
return {
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#getCount
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Gets the count of content types
*
* ##usage
* <pre>
* contentTypeResource.getCount()
* .then(function(data) {
* console.log(data);
* });
* </pre>
*
* @returns {Promise} resourcePromise object.
*
*/
getCount: function () {
return umbRequestHelper.resourcePromise(
$http.get(
@@ -16,6 +35,29 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to retrieve count');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#getAvailableCompositeContentTypes
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Gets the compositions for a content type
*
* ##usage
* <pre>
* contentTypeResource.getAvailableCompositeContentTypes()
* .then(function(data) {
* console.log(data);
* });
* </pre>
*
* @param {Int} contentTypeId id of the content type to retrieve the list of the compositions
* @param {Array} filterContentTypes array of content types to filter out
* @param {Array} filterPropertyTypes array of property aliases to filter out. If specified any content types with the property aliases will be filtered out
* @param {Boolean} isElement whether the composite content types should be applicable for an element type
* @returns {Promise} resourcePromise object.
*
*/
getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes, isElement) {
if (!filterContentTypes) {
filterContentTypes = [];
@@ -86,6 +128,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
* $scope.type = type;
* });
* </pre>
*
* @param {Int} contentTypeId id of the content item to retrive allowed child types for
* @returns {Promise} resourcePromise object.
*
@@ -110,6 +153,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
* @description
* Returns a list of defined property type aliases
*
* ##usage
* <pre>
* contentTypeResource.getAllPropertyTypeAliases()
* .then(function(array) {
* Do stuff...
* });
* </pre>
*
* @returns {Promise} resourcePromise object.
*
*/
@@ -123,6 +174,25 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to retrieve property type aliases');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#getAllStandardFields
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Returns a list of standard property type aliases
*
* ##usage
* <pre>
* contentTypeResource.getAllStandardFields()
* .then(function(array) {
* Do stuff...
* });
* </pre>
*
* @returns {Promise} resourcePromise object.
*
*/
getAllStandardFields: function () {
return umbRequestHelper.resourcePromise(
@@ -133,6 +203,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to retrieve standard fields');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#getPropertyTypeScaffold
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Returns the property display for a given datatype id
*
* ##usage
* <pre>
* contentTypeResource.getPropertyTypeScaffold(1234)
* .then(function(array) {
* Do stuff...
* });
* </pre>
*
* @param {Int} id the id of the datatype
* @returns {Promise} resourcePromise object.
*
*/
getPropertyTypeScaffold: function (id) {
return umbRequestHelper.resourcePromise(
$http.get(
@@ -143,6 +233,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to retrieve property type scaffold');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#getById
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Get the content type with a given id
*
* ##usage
* <pre>
* contentTypeResource.getById("64058D0F-4911-4AB7-B3BA-000D89F00A26")
* .then(function(array) {
* Do stuff...
* });
* </pre>
*
* @param {String} id the guid id of the content type
* @returns {Promise} resourcePromise object.
*
*/
getById: function (id) {
return umbRequestHelper.resourcePromise(
@@ -154,6 +264,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to retrieve content type');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#deleteById
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Delete the content type of a given id
*
* ##usage
* <pre>
* contentTypeResource.deleteById(1234)
* .then(function(array) {
* Do stuff...
* });
* </pre>
*
* @param {Int} id the id of the content type
* @returns {Promise} resourcePromise object.
*
*/
deleteById: function (id) {
return umbRequestHelper.resourcePromise(
@@ -165,6 +295,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to delete content type');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#deleteContainerById
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Delete the content type container of a given id
*
* ##usage
* <pre>
* contentTypeResource.deleteContainerById(1234)
* .then(function(array) {
* Do stuff...
* });
* </pre>
*
* @param {Int} id the id of the content type container
* @returns {Promise} resourcePromise object.
*
*/
deleteContainerById: function (id) {
return umbRequestHelper.resourcePromise(
@@ -197,6 +347,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to retrieve all content types');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#getScaffold
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Returns an empty content type for use as a scaffold when creating a new content type
*
* ##usage
* <pre>
* contentTypeResource.getScaffold(1234)
* .then(function(array) {
* Do stuff...
* });
* </pre>
*
* @param {Int} id the parent id
* @returns {Promise} resourcePromise object.
*
*/
getScaffold: function (parentId) {
return umbRequestHelper.resourcePromise(
@@ -240,14 +410,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
* <pre>
* contentTypeResource.move({ parentId: 1244, id: 123 })
* .then(function() {
* alert("node was moved");
* alert("content type was moved");
* }, function(err){
* alert("node didnt move:" + err.data.Message);
* alert("content type didnt move:" + err.data.Message);
* });
* </pre>
* @param {Object} args arguments object
* @param {Int} args.idd the ID of the node to move
* @param {Int} args.parentId the ID of the parent node to move to
* @param {Int} args.id the ID of the content type to move
* @param {Int} args.parentId the ID of the parent content type to move to
* @returns {Promise} resourcePromise object.
*
*/
@@ -271,6 +441,29 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to move content');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#copy
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Copied a content type underneath a new parentId
*
* ##usage
* <pre>
* contentTypeResource.copy({ parentId: 1244, id: 123 })
* .then(function() {
* alert("content type was copied");
* }, function(err){
* alert("content type didnt copy:" + err.data.Message);
* });
* </pre>
* @param {Object} args arguments object
* @param {Int} args.id the ID of the content type to copy
* @param {Int} args.parentId the ID of the parent content type to copy to
* @returns {Promise} resourcePromise object.
*
*/
copy: function (args) {
if (!args) {
throw "args cannot be null";
@@ -291,6 +484,27 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
'Failed to copy content');
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#createContainer
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Create a new content type container of a given name underneath a given parent item
*
* ##usage
* <pre>
* contentTypeResource.createContainer(1244,"testcontainer")
* .then(function() {
* Do stuff..
* });
* </pre>
*
* @param {Int} parentId the ID of the parent content type underneath which to create the container
* @param {String} name the name of the container
* @returns {Promise} resourcePromise object.
*
*/
createContainer: function (parentId, name) {
return umbRequestHelper.resourcePromise(
@@ -299,6 +513,32 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#createCollection
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Create a collection of a content types
*
* ##usage
* <pre>
* contentTypeResource.createCollection(1244,"testcollectionname",true,"collectionItemName",true,"icon-name","icon-name")
* .then(function() {
* Do stuff..
* });
* </pre>
*
* @param {Int} parentId the ID of the parent content type underneath which to create the collection
* @param {String} collectionName the name of the collection
* @param {Boolean} collectionCreateTemplate true/false to specify whether to create a default template for the collection
* @param {String} collectionItemName the name of the collection item
* @param {Boolean} collectionItemCreateTemplate true/false to specify whether to create a default template for the collection item
* @param {String} collectionIcon the icon for the collection
* @param {String} collectionItemIcon the icon for the collection item
* @returns {Promise} resourcePromise object.
*
*/
createCollection: function (parentId, collectionName, collectionCreateTemplate, collectionItemName, collectionItemCreateTemplate, collectionIcon, collectionItemIcon) {
return umbRequestHelper.resourcePromise(
@@ -307,6 +547,27 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#renameContainer
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Rename a container of a given id
*
* ##usage
* <pre>
* contentTypeResource.renameContainer( 1244,"testcontainer")
* .then(function() {
* Do stuff..
* });
* </pre>
*
* @param {Int} id the ID of the container to rename
* @param {String} name the new name of the container
* @returns {Promise} resourcePromise object.
*
*/
renameContainer: function (id, name) {
return umbRequestHelper.resourcePromise(
@@ -318,6 +579,27 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#export
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Export a content type of a given id.
*
* ##usage
* <pre>
* contentTypeResource.export(1234){
* .then(function() {
* Do stuff..
* });
* </pre>
*
* @param {Int} id the ID of the container to rename
* @param {String} name the new name of the container
* @returns {Promise} resourcePromise object.
*
*/
export: function (id) {
if (!id) {
throw "id cannot be null";
@@ -336,6 +618,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
});
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#import
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Import a content type from a file
*
* ##usage
* <pre>
* contentTypeResource.import("path to file"){
* .then(function() {
* Do stuff..
* });
* </pre>
*
* @param {String} file path of the file to import
* @returns {Promise} resourcePromise object.
*
*/
import: function (file) {
if (!file) {
throw "file cannot be null";
@@ -347,12 +649,52 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
);
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#createDefaultTemplate
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Create a default template for a content type with a given id
*
* ##usage
* <pre>
* contentTypeResource.createDefaultTemplate(1234){
* .then(function() {
* Do stuff..
* });
* </pre>
*
* @param {Int} id the id of the content type for which to create the default template
* @returns {Promise} resourcePromise object.
*
*/
createDefaultTemplate: function (id) {
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })),
'Failed to create default template for content type with id ' + id);
},
/**
* @ngdoc method
* @name umbraco.resources.contentTypeResource#hasContentNodes
* @methodOf umbraco.resources.contentTypeResource
*
* @description
* Returns whether a content type has content nodes
*
* ##usage
* <pre>
* contentTypeResource.hasContentNodes(1234){
* .then(function() {
* Do stuff..
* });
* </pre>
*
* @param {Int} id the id of the content type
* @returns {Promise} resourcePromise object.
*
*/
hasContentNodes: function (id) {
return umbRequestHelper.resourcePromise(
$http.get(

View File

@@ -7,6 +7,25 @@ function elementTypeResource($q, $http, umbRequestHelper) {
return {
/**
* @ngdoc method
* @name umbraco.resources.elementTypeResource#getAll
* @methodOf umbraco.resources.elementTypeResource
*
* @description
* Gets a list of all element types
*
* ##usage
* <pre>
* elementTypeResource.getAll()
* .then(function() {
* alert('Found it!');
* });
* </pre>
*
* @returns {Promise} resourcePromise object.
*
**/
getAll: function () {
return umbRequestHelper.resourcePromise(

View File

@@ -1,18 +1,82 @@
/**
* @ngdoc service
* @name umbraco.resources.modelsBuilderManagementResource
* @description Resources to get information on modelsbuilder status and build models
**/
function modelsBuilderManagementResource($q, $http, umbRequestHelper) {
return {
/**
* @ngdoc method
* @name umbraco.resources.modelsBuilderManagementResource#getModelsOutOfDateStatus
* @methodOf umbraco.resources.modelsBuilderManagementResource
*
* @description
* Gets the status of modelsbuilder
*
* ##usage
* <pre>
* modelsBuilderManagementResource.getModelsOutOfDateStatus()
* .then(function() {
* Do stuff...*
* });
* </pre>
*
* @returns {Promise} resourcePromise object.
*
*/
getModelsOutOfDateStatus: function () {
return umbRequestHelper.resourcePromise(
$http.get(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "GetModelsOutOfDateStatus")),
"Failed to get models out-of-date status");
},
/**
* @ngdoc method
* @name umbraco.resources.modelsBuilderManagementResource#buildModels
* @methodOf umbraco.resources.modelsBuilderManagementResource
*
* @description
* Builds the models
*
* ##usage
* <pre>
* modelsBuilderManagementResource.buildModels()
* .then(function() {
* Do stuff...*
* });
* </pre>
*
* @returns {Promise} resourcePromise object.
*
*/
buildModels: function () {
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "BuildModels")),
"Failed to build models");
},
/**
* @ngdoc method
* @name umbraco.resources.modelsBuilderManagementResource#getDashboard
* @methodOf umbraco.resources.modelsBuilderManagementResource
*
* @description
* Gets the modelsbuilder dashboard
*
* ##usage
* <pre>
* modelsBuilderManagementResource.getDashboard()
* .then(function() {
* Do stuff...*
* });
* </pre>
*
* @returns {Promise} resourcePromise object.
*
*/
getDashboard: function () {
return umbRequestHelper.resourcePromise(
$http.get(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "GetDashboard")),

View File

@@ -9,9 +9,13 @@ LazyLoad.js([
'lib/angular-aria/angular-aria.min.js',
'lib/underscore/underscore-min.js',
'lib/angular-ui-sortable/sortable.js',
'js/utilities.js',
'js/installer.app.js',
'js/umbraco.directives.js',
'js/umbraco.installer.js'
], function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['umbraco']);

View File

@@ -172,7 +172,7 @@ body.umb-drawer-is-visible #mainwrapper{
@media (min-width: 1101px) {
#contentwrapper, #speechbubble { left: 360px; }
.emptySection #contentwrapper {left:0px;}
.emptySection #contentwrapper { left: 0 !important; }
}
//empty section modification

View File

@@ -63,6 +63,11 @@
border-left-color: @white;
}
.umb-button__progress.-black {
border-color: rgba(255, 255, 255, 0.4);
border-left-color: @black;
}
.umb-button__success,
.umb-button__error {
position: absolute;

View File

@@ -10,11 +10,12 @@
font-weight: bold;
display: flex;
align-items: center;
cursor: pointer;
justify-content: space-between;
color: @black;
width: 100%;
&:hover .umb-expansion-panel__expand {
&:hover .umb-expansion-panel__expand,
&:focus .umb-expansion-panel__expand {
color: @gray-6;
}
}

View File

@@ -37,6 +37,7 @@
align-items: center;
flex: 0 0 30px;
margin-right: 5px;
position: relative;
}
.umb-checkbox-list__item-icon {
@@ -44,6 +45,17 @@
font-size: 16px;
}
.umb-checkbox-list__item-icon-wrapper {
position: relative;
.umb-button__progress {
width: 10px;
height: 10px;
margin-left: -10px;
margin-top: -8px;
}
}
.umb-checkbox-list__item-text {
font-size: 14px;
margin-bottom: 0;

View File

@@ -392,7 +392,7 @@
// EDITOR PLACEHOLDER
// -------------------------
.umb-grid .umb-editor-placeholder {
min-height: 65px;
min-height: 110px;
padding: 20px;
padding-bottom: 30px;
position: relative;
@@ -400,7 +400,7 @@
border: 4px dashed @gray-8;
text-align: center;
text-align: -moz-center;
cursor: pointer;
width: 100%;
}
.umb-grid .umb-editor-placeholder i {
@@ -413,6 +413,7 @@
.umb-grid .umb-editor-preview {
position: relative;
width: 100%;
.umb-editor-preview-overlay {
cursor: pointer;

View File

@@ -4,6 +4,7 @@
.umb-nested-content-property-container {
position: relative;
&:not(:last-child){
margin-bottom: 12px;
}
@@ -54,19 +55,19 @@
visibility: visible !important;
}
.umb-nested-content__item--single > .umb-nested-content__content {
.umb-nested-content__item--single {
border: 0;
}
.umb-nested-content__item--single > .umb-nested-content__content > .umb-pane {
> .umb-nested-content__content {
> .umb-pane {
margin: 0;
}
}
}
.umb-nested-content__header-bar {
cursor: pointer;
background-color: @white;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
@@ -78,13 +79,12 @@
padding-right: 60px;
}
}
}
.umb-nested-content__heading {
display: flex;
padding: 15px;
line-height: 20px;
position: relative;
padding: 15px 5px;
color: @ui-option-type;
&:hover {
@@ -92,7 +92,6 @@
}
.umb-nested-content__item-icon {
position: absolute;
margin-top: -3px;
font-size: 22px;
}
@@ -106,10 +105,9 @@
padding-left: 5px;
&.--has-icon {
padding-left: 30px;
padding-left: 10px;
}
}
}
.umb-nested-content__icons {
@@ -125,13 +123,16 @@
.umb-nested-content__item--active > .umb-nested-content__header-bar {
.umb-nested-content__heading {
background-color: @ui-active;
&:hover {
color: @ui-option-type;
}
.umb-nested-content__item-name {
padding-right: 60px;
}
}
.umb-nested-content__icons {
background-color: @ui-active;
&:before {
@@ -140,8 +141,6 @@
}
}
.umb-nested-content__header-bar:hover .umb-nested-content__icons,
.umb-nested-content__header-bar:focus .umb-nested-content__icons,
.umb-nested-content__header-bar:focus-within .umb-nested-content__icons,
@@ -149,8 +148,6 @@
opacity: 1;
}
.umb-nested-content__icon {
background: transparent;
border: 0 none;
@@ -180,9 +177,6 @@
}
}
.umb-nested-content__footer-bar {
margin-top: 20px;
}
@@ -212,7 +206,6 @@
cursor: not-allowed;
}
.umb-nested-content__content {
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
@@ -240,17 +233,17 @@
}
.umb-nested-content__doctypepicker table td.icon-navigation,
.umb-nested-content__doctypepicker i.umb-nested-content__help-icon {
.umb-nested-content__doctypepicker .umb-nested-content__help-icon {
vertical-align: middle;
color: @gray-7;
}
.umb-nested-content__doctypepicker table td.icon-navigation:hover,
.umb-nested-content__doctypepicker i.umb-nested-content__help-icon:hover {
.umb-nested-content__doctypepicker .umb-nested-content__help-icon:hover {
color: @gray-2;
}
.umb-nested-content__doctypepicker i.umb-nested-content__help-icon {
.umb-nested-content__doctypepicker .umb-nested-content__help-action {
margin-left: 10px;
}

View File

@@ -12,7 +12,10 @@
padding-right: 7px;
}
.icon.handle{color: @gray-8;}
.umb-icon.handle,
.icon.handle {
color: @gray-8;
}
// BASE CLASS

View File

@@ -117,6 +117,13 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
}
}
function fixExternalLinks(iframe) {
// Make sure external links don't open inside the iframe
Array.from(iframe.contentDocument.getElementsByTagName("a"))
.filter(a => a.hostname !== location.hostname && !a.target)
.forEach(a => a.target = "_top");
}
var isInit = getParameterByName("init");
if (isInit === "true") {
//do not continue, this is the first load of this new window, if this is passed in it means it's been
@@ -443,6 +450,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.frameLoaded = true;
configureSignalR(iframe);
fixExternalLinks(iframe);
$scope.currentCultureIso = $location.search().culture || null;
};

View File

@@ -97,7 +97,7 @@
</h5>
<ul class="umb-help-list">
<li class="umb-help-list-item" ng-repeat="topic in vm.topics track by $index">
<a class="umb-help-list-item__content" data-element="help-article-{{topic.name}}" target="_blank" rel="noopener" href="" ng-href="{{topic.url}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<a class="umb-help-list-item__content" data-element="help-article-{{topic.name}}" href="#" ng-href="{{topic.url}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv" target="_blank" rel="noopener">
<span>
<span class="umb-help-list-item__title">
<span class="bold">{{topic.name}}</span>
@@ -117,7 +117,7 @@
</h5>
<ul class="umb-help-list">
<li class="umb-help-list-item" ng-repeat="video in vm.videos track by $index">
<a class="umb-help-list-item__content" data-element="help-article-{{video.title}}" target="_blank" rel="noopener" href="" ng-href="{{video.link}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<a class="umb-help-list-item__content" data-element="help-article-{{video.title}}" href="#" ng-href="{{video.link}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv" target="_blank" rel="noopener">
<i class="umb-help-list-item__icon icon-tv-old" aria-hidden="true"></i>
<span class="umb-help-list-item__title">{{video.title}}</span>
<i class="umb-help-list-item__open-icon icon-out" aria-hidden="true"></i>
@@ -128,7 +128,7 @@
<!-- Links -->
<div class="umb-help-section" data-element="help-links" ng-if="vm.hasAccessToSettings">
<a data-element="help-link-umbraco-tv" class="umb-help-badge" target="_blank" rel="noopener" href="https://umbraco.tv?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<a data-element="help-link-umbraco-tv" class="umb-help-badge" href="https://umbraco.tv?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv" target="_blank" rel="noopener">
<i class="umb-help-badge__icon icon-tv-old" aria-hidden="true"></i>
<div class="umb-help-badge__title">
<localize key="help_umbracoTv">Visit umbraco.tv</localize>
@@ -138,7 +138,7 @@
</small>
</a>
<a data-element="help-link-our-umbraco" class="umb-help-badge" target="_blank" rel="noopener" href="https://our.umbraco.com?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=our">
<a data-element="help-link-our-umbraco" class="umb-help-badge" href="https://our.umbraco.com?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=our" target="_blank" rel="noopener">
<i class="umb-help-badge__icon icon-favorite" aria-hidden="true"></i>
<div class="umb-help-badge__title">

Some files were not shown because too many files have changed in this diff Show More