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:
2
.github/BUILD.md
vendored
2
.github/BUILD.md
vendored
@@ -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
2
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Resources;
|
||||
|
||||
[assembly: AssemblyCompany("Umbraco")]
|
||||
[assembly: AssemblyCopyright("Copyright © Umbraco 2020")]
|
||||
[assembly: AssemblyCopyright("Copyright © Umbraco 2021")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
src/Umbraco.Core/Exceptions/UnattendedInstallException.cs
Normal file
46
src/Umbraco.Core/Exceptions/UnattendedInstallException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -34,5 +34,6 @@ namespace Umbraco.Core.Models
|
||||
void Add(IProperty property);
|
||||
|
||||
int Count { get; }
|
||||
void ClearCollectionChangedEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -57,5 +57,6 @@ namespace Umbraco.Core
|
||||
|
||||
void Configure(RuntimeLevel level, RuntimeLevelReason reason);
|
||||
|
||||
void DoUnattendedInstall();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Telemetry
|
||||
{
|
||||
public class TelemetryMarkerComposer : ComponentComposer<TelemetryMarkerComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Umbraco.Web.Install
|
||||
return true;
|
||||
}
|
||||
|
||||
return _databaseBuilder.HasSomeNonDefaultUser() == false;
|
||||
return _databaseBuilder.IsUmbracoInstalled() == false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>";
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
'lib/umbraco/NamespaceManager.js',
|
||||
'lib/umbraco/LegacySpeechBubble.js',
|
||||
|
||||
'js/utilities.js',
|
||||
|
||||
'js/app.js',
|
||||
|
||||
'js/umbraco.resources.js',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
'lib/angular/angular.js',
|
||||
'lib/underscore/underscore-min.js',
|
||||
'lib/umbraco/Extensions.js',
|
||||
'js/utilities.js',
|
||||
'js/app.js',
|
||||
'js/umbraco.resources.js',
|
||||
'js/umbraco.services.js',
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>
|
||||
|
||||
179
src/Umbraco.Tests/Models/RangeTests.cs
Normal file
179
src/Umbraco.Tests/Models/RangeTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 || '');
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
return collection;
|
||||
}
|
||||
|
||||
if (angular.isUndefined(property)) {
|
||||
if (Utilities.isUndefined(property)) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")),
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
.icon.handle{color: @gray-8;}
|
||||
.umb-icon.handle,
|
||||
.icon.handle {
|
||||
color: @gray-8;
|
||||
}
|
||||
|
||||
|
||||
// BASE CLASS
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user