diff --git a/.gitignore b/.gitignore
index 0073675d82..ebee9f6ae9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -129,3 +129,4 @@ src/*.boltdata/
/src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.front.js
src/umbraco.sln.ide/*
build/UmbracoCms.*/
+src/.vs/
diff --git a/build/Build.proj b/build/Build.proj
index bdaa5b9c0c..051e02de5c 100644
--- a/build/Build.proj
+++ b/build/Build.proj
@@ -68,6 +68,12 @@
.$(BUILD_RELEASE)-$(BUILD_COMMENT)
+
+ .$(BUILD_RELEASE)-$(BUILD_NIGHTLY)
+
+
+ .$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)
+
Release
@@ -271,9 +277,11 @@
$(BUILD_RELEASE)
$(BUILD_RELEASE)-$(BUILD_COMMENT)
+ $(BUILD_RELEASE)-$(BUILD_NIGHTLY)
+ $(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)
-
+
+
+
+
+
+
+
+
diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt
index 6ce8416610..f767faaa60 100644
--- a/build/UmbracoVersion.txt
+++ b/build/UmbracoVersion.txt
@@ -1,3 +1,3 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.3.0
-beta
\ No newline at end of file
+beta2
\ No newline at end of file
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 29ba870d90..e4258e943d 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.3.0")]
-[assembly: AssemblyInformationalVersion("7.3.0-beta")]
\ No newline at end of file
+[assembly: AssemblyInformationalVersion("7.3.0-beta2")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs
index aae174106e..b48d4eb5e9 100644
--- a/src/Umbraco.Core/ApplicationContext.cs
+++ b/src/Umbraco.Core/ApplicationContext.cs
@@ -10,6 +10,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Profiling;
using Umbraco.Core.Services;
+using Umbraco.Core.Sync;
namespace Umbraco.Core
@@ -220,18 +221,46 @@ namespace Umbraco.Core
}
///
- /// The original/first url that the web application executes
+ /// The application url.
///
///
- /// we need to set the initial url in our ApplicationContext, this is so our keep alive service works and this must
- /// exist on a global context because the keep alive service doesn't run in a web context.
- /// we are NOT going to put a lock on this because locking will slow down the application and we don't really care
- /// if two threads write to this at the exact same time during first page hit.
- /// see: http://issues.umbraco.org/issue/U4-2059
+ /// The application url is the url that should be used by services to talk to the application,
+ /// eg keep alive or scheduled publishing services. It must exist on a global context because
+ /// some of these services may not run within a web context.
+ /// The format of the application url is:
+ /// - has a scheme (http or https)
+ /// - has the SystemDirectories.Umbraco path
+ /// - does not end with a slash
+ /// It is initialized on the first request made to the server, by UmbracoModule.EnsureApplicationUrl:
+ /// - if umbracoSettings:settings/web.routing/@appUrl is set, use the value (new setting)
+ /// - if umbracoSettings:settings/scheduledTasks/@baseUrl is set, use the value (backward compatibility)
+ /// - otherwise, use the url of the (first) request.
+ /// Not locking, does not matter if several threads write to this.
+ /// See also issues:
+ /// - http://issues.umbraco.org/issue/U4-2059
+ /// - http://issues.umbraco.org/issue/U4-6788
+ /// - http://issues.umbraco.org/issue/U4-5728
+ /// - http://issues.umbraco.org/issue/U4-5391
///
- internal string OriginalRequestUrl { get; set; }
+ internal string UmbracoApplicationUrl {
+ get
+ {
+ // if initialized, return
+ if (_umbracoApplicationUrl != null) return _umbracoApplicationUrl;
+ // try settings
+ ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(this, ProfilingLogger.Logger, UmbracoConfig.For.UmbracoSettings());
+ // and return what we have, may be null
+ return _umbracoApplicationUrl;
+ }
+ set
+ {
+ _umbracoApplicationUrl = value;
+ }
+ }
+
+ internal string _umbracoApplicationUrl; // internal for tests
private Lazy _configured;
private void Init()
@@ -257,14 +286,14 @@ namespace Umbraco.Core
{
//we haven't executed this migration in this environment, so even though the config versions match,
// this db has not been updated.
- ProfilingLogger.Logger.Debug("The migration for version: '" + currentVersion + " has not been executed, there is no record in the database");
+ ProfilingLogger.Logger.Debug(string.Format("The migration for version: '{0} has not been executed, there is no record in the database", currentVersion.ToSemanticString()));
ok = false;
}
}
}
else
{
- ProfilingLogger.Logger.Debug("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
+ ProfilingLogger.Logger.Debug(string.Format("CurrentVersion different from configStatus: '{0}','{1}'", currentVersion.ToSemanticString(), configStatus));
}
return ok;
diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs
index 0a9c79a80e..608b19a700 100644
--- a/src/Umbraco.Core/AsyncLock.cs
+++ b/src/Umbraco.Core/AsyncLock.cs
@@ -63,13 +63,13 @@ namespace Umbraco.Core
// for anonymous semaphore, use the unique releaser, else create a new one
return _semaphore != null
? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore)
- : (IDisposable)new NamedSemaphoreReleaser(_semaphore2);
+ : new NamedSemaphoreReleaser(_semaphore2);
}
public Task LockAsync()
{
var wait = _semaphore != null
- ? _semaphore.WaitAsync()
+ ? _semaphore.WaitAsync()
: WaitOneAsync(_semaphore2);
return wait.IsCompleted
@@ -79,6 +79,19 @@ namespace Umbraco.Core
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
+ public Task LockAsync(int millisecondsTimeout)
+ {
+ var wait = _semaphore != null
+ ? _semaphore.WaitAsync(millisecondsTimeout)
+ : WaitOneAsync(_semaphore2, millisecondsTimeout);
+
+ return wait.IsCompleted
+ ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
+ : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
+ this, CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
+ }
+
public IDisposable Lock()
{
if (_semaphore != null)
@@ -174,7 +187,7 @@ namespace Umbraco.Core
// F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex...
// version below should be OK
- private static Task WaitOneAsync(WaitHandle handle)
+ private static Task WaitOneAsync(WaitHandle handle, int millisecondsTimeout = Timeout.Infinite)
{
var tcs = new TaskCompletionSource();
var callbackHandleInitLock = new object();
@@ -197,7 +210,7 @@ namespace Umbraco.Core
}
},
/*state:*/ null,
- /*millisecondsTimeOutInterval:*/ Timeout.Infinite,
+ /*millisecondsTimeOutInterval:*/ millisecondsTimeout,
/*executeOnlyOnce:*/ true);
}
diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index 329fa598c1..2c3855727b 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -429,7 +429,7 @@ namespace Umbraco.Core.Configuration
try
{
string configStatus = ConfigurationStatus;
- string currentVersion = UmbracoVersion.GetSemanticVersion().ToString();
+ string currentVersion = UmbracoVersion.GetSemanticVersion().ToSemanticString();
if (currentVersion != configStatus)
@@ -596,7 +596,7 @@ namespace Umbraco.Core.Configuration
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
public static string CurrentVersion
{
- get { return UmbracoVersion.GetSemanticVersion().ToString(); }
+ get { return UmbracoVersion.GetSemanticVersion().ToSemanticString(); }
}
///
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
index f3d42b6904..2998fc2f78 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
@@ -11,6 +11,8 @@
bool DisableFindContentByIdPath { get; }
string UrlProviderMode { get; }
+
+ string UmbracoApplicationUrl { get; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
index f5b71eb2c7..1ed9bc034c 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
@@ -30,8 +30,13 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")]
public string UrlProviderMode
{
- get { return (string)base["urlProviderMode"]; }
+ get { return (string) base["urlProviderMode"]; }
}
+ [ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)]
+ public string UmbracoApplicationUrl
+ {
+ get { return (string)base["umbracoApplicationUrl"]; }
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index d96b9f146b..49bea68719 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
///
/// The version comment.
- public static string CurrentComment { get { return "beta.2"; } }
+ public static string CurrentComment { get { return "beta2"; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs
index c1d5bd35e3..3456ff1cfa 100644
--- a/src/Umbraco.Core/Constants-Conventions.cs
+++ b/src/Umbraco.Core/Constants-Conventions.cs
@@ -27,6 +27,7 @@ namespace Umbraco.Core
///
/// The root id for all top level dictionary items
///
+ [Obsolete("There is no dictionary root item id anymore, it is simply null")]
public const string DictionaryItemRootId = "41c7638d-f529-4bff-853e-59a0c2fb1bde";
}
diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs
index 0d7c2f41da..13dee96b97 100644
--- a/src/Umbraco.Core/Constants-Web.cs
+++ b/src/Umbraco.Core/Constants-Web.cs
@@ -26,6 +26,7 @@
public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
+ public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
///
/// The prefix used for external identity providers for their authentication type
diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs
index 8f09fbc2bf..998bda32f2 100644
--- a/src/Umbraco.Core/CoreBootManager.cs
+++ b/src/Umbraco.Core/CoreBootManager.cs
@@ -76,7 +76,9 @@ namespace Umbraco.Core
_profilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler);
- _timer = _profilingLogger.TraceDuration("Umbraco application starting", "Umbraco application startup complete");
+ _timer = _profilingLogger.TraceDuration(
+ string.Format("Umbraco application ({0}) starting", UmbracoVersion.GetSemanticVersion().ToSemanticString()),
+ "Umbraco application startup complete");
CreateApplicationCache();
diff --git a/src/Umbraco.Core/Logging/AppDomainTokenFormatter.cs b/src/Umbraco.Core/Logging/AppDomainTokenConverter.cs
similarity index 100%
rename from src/Umbraco.Core/Logging/AppDomainTokenFormatter.cs
rename to src/Umbraco.Core/Logging/AppDomainTokenConverter.cs
diff --git a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs
new file mode 100644
index 0000000000..74a1de81f4
--- /dev/null
+++ b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs
@@ -0,0 +1,105 @@
+using System;
+using log4net.Appender;
+using log4net.Core;
+using log4net.Util;
+
+namespace Umbraco.Core.Logging
+{
+ ///
+ /// Based on https://github.com/cjbhaines/Log4Net.Async
+ ///
+ public abstract class AsyncForwardingAppenderBase : ForwardingAppender
+ {
+ #region Private Members
+
+ private const FixFlags DefaultFixFlags = FixFlags.Partial;
+ private FixFlags _fixFlags = DefaultFixFlags;
+ private LoggingEventHelper _loggingEventHelper;
+
+ #endregion Private Members
+
+ #region Properties
+
+ public FixFlags Fix
+ {
+ get { return _fixFlags; }
+ set { SetFixFlags(value); }
+ }
+
+ ///
+ /// The logger name that will be used for logging internal errors.
+ ///
+ protected abstract string InternalLoggerName { get; }
+
+ public abstract int? BufferSize { get; set; }
+
+ #endregion Properties
+
+ public override void ActivateOptions()
+ {
+ base.ActivateOptions();
+ _loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags);
+ InitializeAppenders();
+ }
+
+ #region Appender Management
+
+ public override void AddAppender(IAppender newAppender)
+ {
+ base.AddAppender(newAppender);
+ SetAppenderFixFlags(newAppender);
+ }
+
+ private void SetFixFlags(FixFlags newFixFlags)
+ {
+ if (newFixFlags != _fixFlags)
+ {
+ _loggingEventHelper.Fix = newFixFlags;
+ _fixFlags = newFixFlags;
+ InitializeAppenders();
+ }
+ }
+
+ private void InitializeAppenders()
+ {
+ foreach (var appender in Appenders)
+ {
+ SetAppenderFixFlags(appender);
+ }
+ }
+
+ private void SetAppenderFixFlags(IAppender appender)
+ {
+ var bufferingAppender = appender as BufferingAppenderSkeleton;
+ if (bufferingAppender != null)
+ {
+ bufferingAppender.Fix = Fix;
+ }
+ }
+
+ #endregion Appender Management
+
+ #region Forwarding
+
+ protected void ForwardInternalError(string message, Exception exception, Type thisType)
+ {
+ LogLog.Error(thisType, message, exception);
+ var loggingEvent = _loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception);
+ ForwardLoggingEvent(loggingEvent, thisType);
+ }
+
+ protected void ForwardLoggingEvent(LoggingEvent loggingEvent, Type thisType)
+ {
+ try
+ {
+ base.Append(loggingEvent);
+ }
+ catch (Exception exception)
+ {
+ LogLog.Error(thisType, "Unable to forward logging event", exception);
+ }
+ }
+
+ #endregion Forwarding
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs
index 56d04c8426..cb58ebbfaa 100644
--- a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs
+++ b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs
@@ -1,276 +1,276 @@
+using log4net.Core;
+using log4net.Util;
using System;
+using System.Runtime.Remoting.Messaging;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using log4net.Appender;
-using log4net.Core;
-using log4net.Util;
namespace Umbraco.Core.Logging
{
- ///
- /// Based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/
+ ///
+ /// Based on https://github.com/cjbhaines/Log4Net.Async
+ /// which is based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/
///
public class AsynchronousRollingFileAppender : RollingFileAppender
{
- private readonly ManualResetEvent _manualResetEvent;
- private int _bufferOverflowCounter;
- private bool _forceStop;
- private bool _hasFinished;
- private DateTime _lastLoggedBufferOverflow;
- private bool _logBufferOverflow;
- private RingBuffer _pendingAppends;
- private int _queueSizeLimit = 1000;
- private bool _shuttingDown;
+ private RingBuffer pendingAppends;
+ private readonly ManualResetEvent manualResetEvent;
+ private bool shuttingDown;
+ private bool hasFinished;
+ private bool forceStop;
+ private bool logBufferOverflow;
+ private int bufferOverflowCounter;
+ private DateTime lastLoggedBufferOverflow;
+ private int queueSizeLimit = 1000;
+ public int QueueSizeLimit
+ {
+ get
+ {
+ return queueSizeLimit;
+ }
+ set
+ {
+ queueSizeLimit = value;
+ }
+ }
- public AsynchronousRollingFileAppender()
- {
- _manualResetEvent = new ManualResetEvent(false);
- }
+ public AsynchronousRollingFileAppender()
+ {
+ manualResetEvent = new ManualResetEvent(false);
+ }
- public int QueueSizeLimit
- {
- get { return _queueSizeLimit; }
- set { _queueSizeLimit = value; }
- }
+ public override void ActivateOptions()
+ {
+ base.ActivateOptions();
+ pendingAppends = new RingBuffer(QueueSizeLimit);
+ pendingAppends.BufferOverflow += OnBufferOverflow;
+ StartAppendTask();
+ }
- public override void ActivateOptions()
- {
- base.ActivateOptions();
- _pendingAppends = new RingBuffer(QueueSizeLimit);
- _pendingAppends.BufferOverflow += OnBufferOverflow;
- StartAppendTask();
- }
+ protected override void Append(LoggingEvent[] loggingEvents)
+ {
+ Array.ForEach(loggingEvents, Append);
+ }
- protected override void Append(LoggingEvent[] loggingEvents)
- {
- Array.ForEach(loggingEvents, Append);
- }
+ protected override void Append(LoggingEvent loggingEvent)
+ {
+ if (FilterEvent(loggingEvent))
+ {
+ pendingAppends.Enqueue(loggingEvent);
+ }
+ }
- protected override void Append(LoggingEvent loggingEvent)
- {
- if (FilterEvent(loggingEvent))
- {
- _pendingAppends.Enqueue(loggingEvent);
- }
- }
+ protected override void OnClose()
+ {
+ shuttingDown = true;
+ manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
- protected override void OnClose()
- {
- _shuttingDown = true;
- _manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
+ if (!hasFinished)
+ {
+ forceStop = true;
+ base.Append(new LoggingEvent(new LoggingEventData
+ {
+ Level = Level.Error,
+ Message = "Unable to clear out the AsyncRollingFileAppender buffer in the allotted time, forcing a shutdown",
+ TimeStamp = DateTime.UtcNow,
+ Identity = "",
+ ExceptionString = "",
+ UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "",
+ Domain = AppDomain.CurrentDomain.FriendlyName,
+ ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),
+ LocationInfo = new LocationInfo(this.GetType().Name, "OnClose", "AsyncRollingFileAppender.cs", "75"),
+ LoggerName = this.GetType().FullName,
+ Properties = new PropertiesDictionary(),
+ })
+ );
+ }
- if (!_hasFinished)
- {
- _forceStop = true;
- var windowsIdentity = WindowsIdentity.GetCurrent();
+ base.OnClose();
+ }
- var logEvent = new LoggingEvent(new LoggingEventData
- {
- Level = global::log4net.Core.Level.Error,
- Message =
- "Unable to clear out the AsynchronousRollingFileAppender buffer in the allotted time, forcing a shutdown",
- TimeStamp = DateTime.UtcNow,
- Identity = "",
- ExceptionString = "",
- UserName = windowsIdentity != null ? windowsIdentity.Name : "",
- Domain = AppDomain.CurrentDomain.FriendlyName,
- ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),
- LocationInfo =
- new LocationInfo(this.GetType().Name, "OnClose", "AsynchronousRollingFileAppender.cs", "59"),
- LoggerName = this.GetType().FullName,
- Properties = new PropertiesDictionary(),
- });
+ private void StartAppendTask()
+ {
+ if (!shuttingDown)
+ {
+ Task appendTask = new Task(AppendLoggingEvents, TaskCreationOptions.LongRunning);
+ appendTask.LogErrors(LogAppenderError).ContinueWith(x => StartAppendTask()).LogErrors(LogAppenderError);
+ appendTask.Start();
+ }
+ }
- if (this.DateTimeStrategy != null)
- {
- base.Append(logEvent);
- }
- }
+ private void LogAppenderError(string logMessage, Exception exception)
+ {
+ base.Append(new LoggingEvent(new LoggingEventData
+ {
+ Level = Level.Error,
+ Message = "Appender exception: " + logMessage,
+ TimeStamp = DateTime.UtcNow,
+ Identity = "",
+ ExceptionString = exception.ToString(),
+ UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "",
+ Domain = AppDomain.CurrentDomain.FriendlyName,
+ ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),
+ LocationInfo = new LocationInfo(this.GetType().Name, "LogAppenderError", "AsyncRollingFileAppender.cs", "152"),
+ LoggerName = this.GetType().FullName,
+ Properties = new PropertiesDictionary(),
+ }));
+ }
- base.OnClose();
- }
+ private void AppendLoggingEvents()
+ {
+ LoggingEvent loggingEventToAppend;
+ while (!shuttingDown)
+ {
+ if (logBufferOverflow)
+ {
+ LogBufferOverflowError();
+ logBufferOverflow = false;
+ bufferOverflowCounter = 0;
+ lastLoggedBufferOverflow = DateTime.UtcNow;
+ }
- private void StartAppendTask()
- {
- if (!_shuttingDown)
- {
- Task appendTask = new Task(AppendLoggingEvents, TaskCreationOptions.LongRunning);
- appendTask.LogErrors(LogAppenderError).ContinueWith(x => StartAppendTask()).LogErrors(LogAppenderError);
- appendTask.Start();
- }
- }
+ while (!pendingAppends.TryDequeue(out loggingEventToAppend))
+ {
+ Thread.Sleep(10);
+ if (shuttingDown)
+ {
+ break;
+ }
+ }
+ if (loggingEventToAppend == null)
+ {
+ continue;
+ }
- private void LogAppenderError(string logMessage, Exception exception)
- {
- var windowsIdentity = WindowsIdentity.GetCurrent();
- base.Append(new LoggingEvent(new LoggingEventData
- {
- Level = Level.Error,
- Message = "Appender exception: " + logMessage,
- TimeStamp = DateTime.UtcNow,
- Identity = "",
- ExceptionString = exception.ToString(),
- UserName = windowsIdentity != null ? windowsIdentity.Name : "",
- Domain = AppDomain.CurrentDomain.FriendlyName,
- ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),
- LocationInfo =
- new LocationInfo(this.GetType().Name,
- "LogAppenderError",
- "AsynchronousRollingFileAppender.cs",
- "100"),
- LoggerName = this.GetType().FullName,
- Properties = new PropertiesDictionary(),
- }));
- }
+ try
+ {
+ base.Append(loggingEventToAppend);
+ }
+ catch
+ {
+ }
+ }
- private void AppendLoggingEvents()
- {
- LoggingEvent loggingEventToAppend;
- while (!_shuttingDown)
- {
- if (_logBufferOverflow)
- {
- LogBufferOverflowError();
- _logBufferOverflow = false;
- _bufferOverflowCounter = 0;
- _lastLoggedBufferOverflow = DateTime.UtcNow;
- }
+ while (pendingAppends.TryDequeue(out loggingEventToAppend) && !forceStop)
+ {
+ try
+ {
+ base.Append(loggingEventToAppend);
+ }
+ catch
+ {
+ }
+ }
+ hasFinished = true;
+ manualResetEvent.Set();
+ }
- while (!_pendingAppends.TryDequeue(out loggingEventToAppend))
- {
- Thread.Sleep(10);
- if (_shuttingDown)
- {
- break;
- }
- }
- if (loggingEventToAppend == null)
- {
- continue;
- }
+ private void LogBufferOverflowError()
+ {
+ base.Append(new LoggingEvent(new LoggingEventData
+ {
+ Level = Level.Error,
+ Message = string.Format("Buffer overflow. {0} logging events have been lost in the last 30 seconds. [QueueSizeLimit: {1}]", bufferOverflowCounter, QueueSizeLimit),
+ TimeStamp = DateTime.UtcNow,
+ Identity = "",
+ ExceptionString = "",
+ UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "",
+ Domain = AppDomain.CurrentDomain.FriendlyName,
+ ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),
+ LocationInfo = new LocationInfo(this.GetType().Name, "LogBufferOverflowError", "AsyncRollingFileAppender.cs", "152"),
+ LoggerName = this.GetType().FullName,
+ Properties = new PropertiesDictionary(),
+ }));
+ }
- try
- {
- base.Append(loggingEventToAppend);
- }
- catch
- {
- }
- }
+ private void OnBufferOverflow(object sender, EventArgs eventArgs)
+ {
+ bufferOverflowCounter++;
+ if (logBufferOverflow == false)
+ {
+ if (lastLoggedBufferOverflow < DateTime.UtcNow.AddSeconds(-30))
+ {
+ logBufferOverflow = true;
+ }
+ }
+ }
+ }
- while (_pendingAppends.TryDequeue(out loggingEventToAppend) && !_forceStop)
- {
- try
- {
- base.Append(loggingEventToAppend);
- }
- catch
- {
- }
- }
- _hasFinished = true;
- _manualResetEvent.Set();
- }
+ internal interface IQueue
+ {
+ void Enqueue(T item);
+ bool TryDequeue(out T ret);
+ }
- private void LogBufferOverflowError()
- {
- var windowsIdentity = WindowsIdentity.GetCurrent();
- base.Append(new LoggingEvent(new LoggingEventData
- {
- Level = Level.Error,
- Message =
- string.Format(
- "Buffer overflow. {0} logging events have been lost in the last 30 seconds. [QueueSizeLimit: {1}]",
- _bufferOverflowCounter,
- QueueSizeLimit),
- TimeStamp = DateTime.UtcNow,
- Identity = "",
- ExceptionString = "",
- UserName = windowsIdentity != null ? windowsIdentity.Name : "",
- Domain = AppDomain.CurrentDomain.FriendlyName,
- ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),
- LocationInfo =
- new LocationInfo(this.GetType().Name,
- "LogBufferOverflowError",
- "AsynchronousRollingFileAppender.cs",
- "172"),
- LoggerName = this.GetType().FullName,
- Properties = new PropertiesDictionary(),
- }));
- }
+ internal class RingBuffer : IQueue
+ {
+ private readonly object lockObject = new object();
+ private readonly T[] buffer;
+ private readonly int size;
+ private int readIndex = 0;
+ private int writeIndex = 0;
+ private bool bufferFull = false;
- private void OnBufferOverflow(object sender, EventArgs eventArgs)
- {
- _bufferOverflowCounter++;
- if (_logBufferOverflow == false)
- {
- if (_lastLoggedBufferOverflow < DateTime.UtcNow.AddSeconds(-30))
- {
- _logBufferOverflow = true;
- }
- }
- }
+ public int Size { get { return size; } }
- private class RingBuffer
- {
- private readonly object _lockObject = new object();
- private readonly T[] _buffer;
- private readonly int _size;
- private int _readIndex = 0;
- private int _writeIndex = 0;
- private bool _bufferFull = false;
+ public event Action BufferOverflow;
- public event Action BufferOverflow;
+ public RingBuffer(int size)
+ {
+ this.size = size;
+ buffer = new T[size];
+ }
- public RingBuffer(int size)
- {
- this._size = size;
- _buffer = new T[size];
- }
+ public void Enqueue(T item)
+ {
+ var bufferWasFull = false;
+ lock (lockObject)
+ {
+ buffer[writeIndex] = item;
+ writeIndex = (++writeIndex) % size;
+ if (bufferFull)
+ {
+ bufferWasFull = true;
+ readIndex = writeIndex;
+ }
+ else if (writeIndex == readIndex)
+ {
+ bufferFull = true;
+ }
+ }
- public void Enqueue(T item)
- {
- lock (_lockObject)
- {
- _buffer[_writeIndex] = item;
- _writeIndex = (++_writeIndex) % _size;
- if (_bufferFull)
- {
- if (BufferOverflow != null)
- {
- BufferOverflow(this, EventArgs.Empty);
- }
- _readIndex = _writeIndex;
- }
- else if (_writeIndex == _readIndex)
- {
- _bufferFull = true;
- }
- }
- }
+ if (bufferWasFull)
+ {
+ if (BufferOverflow != null)
+ {
+ BufferOverflow(this, EventArgs.Empty);
+ }
+ }
+ }
- public bool TryDequeue(out T ret)
- {
- if (_readIndex == _writeIndex && !_bufferFull)
- {
- ret = default(T);
- return false;
- }
- lock (_lockObject)
- {
- if (_readIndex == _writeIndex && !_bufferFull)
- {
- ret = default(T);
- return false;
- }
+ public bool TryDequeue(out T ret)
+ {
+ if (readIndex == writeIndex && !bufferFull)
+ {
+ ret = default(T);
+ return false;
+ }
+ lock (lockObject)
+ {
+ if (readIndex == writeIndex && !bufferFull)
+ {
+ ret = default(T);
+ return false;
+ }
- ret = _buffer[_readIndex];
- _readIndex = (++_readIndex) % _size;
- _bufferFull = false;
- return true;
- }
- }
- }
- }
+ ret = buffer[readIndex];
+ buffer[readIndex] = default(T);
+ readIndex = (++readIndex) % size;
+ bufferFull = false;
+ return true;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Logging/Logger.cs b/src/Umbraco.Core/Logging/Logger.cs
index 58f696d82b..ae8bb60fcd 100644
--- a/src/Umbraco.Core/Logging/Logger.cs
+++ b/src/Umbraco.Core/Logging/Logger.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
@@ -12,15 +13,20 @@ namespace Umbraco.Core.Logging
/// Used for logging
///
public class Logger : ILogger
- {
+ {
+
public Logger(FileInfo log4NetConfigFile)
+ :this()
{
XmlConfigurator.Configure(log4NetConfigFile);
}
private Logger()
{
-
+ //Add custom global properties to the log4net context that we can use in our logging output
+
+ log4net.GlobalContext.Properties["processId"] = Process.GetCurrentProcess().Id;
+ log4net.GlobalContext.Properties["appDomainId"] = AppDomain.CurrentDomain.Id;
}
///
@@ -53,31 +59,19 @@ namespace Umbraco.Core.Logging
return LogManager.GetLogger(getTypeFromInstance.GetType());
}
-
- ///
- /// Useful if the logger itself is running on another thread
- ///
- ///
- ///
- private string PrefixThreadId(string generateMessageFormat)
- {
- return "[Thread " + Thread.CurrentThread.ManagedThreadId + "] " + generateMessageFormat;
- }
-
+
public void Error(Type callingType, string message, Exception exception)
{
var logger = LogManager.GetLogger(callingType);
if (logger != null)
- logger.Error(PrefixThreadId(message), exception);
+ logger.Error((message), exception);
}
-
-
public void Warn(Type callingType, string message, params Func[] formatItems)
{
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
- logger.WarnFormat(PrefixThreadId(message), formatItems.Select(x => x.Invoke()).ToArray());
+ logger.WarnFormat((message), formatItems.Select(x => x.Invoke()).ToArray());
}
public void Warn(Type callingType, string message, bool showHttpTrace, params Func[] formatItems)
@@ -92,7 +86,7 @@ namespace Umbraco.Core.Logging
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
- logger.WarnFormat(PrefixThreadId(message), formatItems.Select(x => x.Invoke()).ToArray());
+ logger.WarnFormat((message), formatItems.Select(x => x.Invoke()).ToArray());
}
@@ -105,7 +99,7 @@ namespace Umbraco.Core.Logging
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
var executedParams = formatItems.Select(x => x.Invoke()).ToArray();
- logger.WarnFormat(PrefixThreadId(message) + ". Exception: " + e, executedParams);
+ logger.WarnFormat((message) + ". Exception: " + e, executedParams);
}
///
@@ -117,7 +111,7 @@ namespace Umbraco.Core.Logging
{
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsInfoEnabled == false) return;
- logger.Info(PrefixThreadId(generateMessage.Invoke()));
+ logger.Info((generateMessage.Invoke()));
}
///
@@ -131,7 +125,7 @@ namespace Umbraco.Core.Logging
var logger = LogManager.GetLogger(type);
if (logger == null || logger.IsInfoEnabled == false) return;
var executedParams = formatItems.Select(x => x.Invoke()).ToArray();
- logger.InfoFormat(PrefixThreadId(generateMessageFormat), executedParams);
+ logger.InfoFormat((generateMessageFormat), executedParams);
}
@@ -144,7 +138,7 @@ namespace Umbraco.Core.Logging
{
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsDebugEnabled == false) return;
- logger.Debug(PrefixThreadId(generateMessage.Invoke()));
+ logger.Debug((generateMessage.Invoke()));
}
///
@@ -158,7 +152,7 @@ namespace Umbraco.Core.Logging
var logger = LogManager.GetLogger(type);
if (logger == null || logger.IsDebugEnabled == false) return;
var executedParams = formatItems.Select(x => x.Invoke()).ToArray();
- logger.DebugFormat(PrefixThreadId(generateMessageFormat), executedParams);
+ logger.DebugFormat((generateMessageFormat), executedParams);
}
diff --git a/src/Umbraco.Core/Logging/LoggingEventContext.cs b/src/Umbraco.Core/Logging/LoggingEventContext.cs
new file mode 100644
index 0000000000..159af4266b
--- /dev/null
+++ b/src/Umbraco.Core/Logging/LoggingEventContext.cs
@@ -0,0 +1,17 @@
+using log4net.Core;
+
+namespace Umbraco.Core.Logging
+{
+ ///
+ /// Based on https://github.com/cjbhaines/Log4Net.Async
+ ///
+ internal class LoggingEventContext
+ {
+ public LoggingEventContext(LoggingEvent loggingEvent)
+ {
+ LoggingEvent = loggingEvent;
+ }
+
+ public LoggingEvent LoggingEvent { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Logging/LoggingEventHelper.cs b/src/Umbraco.Core/Logging/LoggingEventHelper.cs
new file mode 100644
index 0000000000..c788e115f2
--- /dev/null
+++ b/src/Umbraco.Core/Logging/LoggingEventHelper.cs
@@ -0,0 +1,31 @@
+using System;
+using log4net.Core;
+
+namespace Umbraco.Core.Logging
+{
+ ///
+ /// Based on https://github.com/cjbhaines/Log4Net.Async
+ ///
+ internal class LoggingEventHelper
+ {
+ // needs to be a seperate class so that location is determined correctly by log4net when required
+
+ private static readonly Type HelperType = typeof(LoggingEventHelper);
+ private readonly string loggerName;
+
+ public FixFlags Fix { get; set; }
+
+ public LoggingEventHelper(string loggerName, FixFlags fix)
+ {
+ this.loggerName = loggerName;
+ Fix = fix;
+ }
+
+ public LoggingEvent CreateLoggingEvent(Level level, string message, Exception exception)
+ {
+ var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception);
+ loggingEvent.Fix = Fix;
+ return loggingEvent;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs
new file mode 100644
index 0000000000..92c4f7589b
--- /dev/null
+++ b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs
@@ -0,0 +1,307 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using System.Threading.Tasks;
+using log4net.Core;
+using log4net.Util;
+
+namespace Umbraco.Core.Logging
+{
+ ///
+ /// An asynchronous appender based on
+ ///
+ ///
+ /// Based on https://github.com/cjbhaines/Log4Net.Async
+ ///
+ public class ParallelForwardingAppender : AsyncForwardingAppenderBase, IDisposable
+ {
+ #region Private Members
+
+ private const int DefaultBufferSize = 1000;
+ private BlockingCollection _loggingEvents;
+ private CancellationTokenSource _loggingCancelationTokenSource;
+ private CancellationToken _loggingCancelationToken;
+ private Task _loggingTask;
+ private Double _shutdownFlushTimeout = 5;
+ private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(5);
+ private static readonly Type ThisType = typeof(ParallelForwardingAppender);
+ private volatile bool _shutDownRequested;
+ private int? _bufferSize = DefaultBufferSize;
+
+ #endregion Private Members
+
+ #region Properties
+
+ ///
+ /// Gets or sets the number of LoggingEvents that will be buffered. Set to null for unlimited.
+ ///
+ public override int? BufferSize
+ {
+ get { return _bufferSize; }
+ set { _bufferSize = value; }
+ }
+
+ public int BufferEntryCount
+ {
+ get
+ {
+ if (_loggingEvents == null) return 0;
+ return _loggingEvents.Count;
+ }
+ }
+
+ ///
+ /// Gets or sets the time period in which the system will wait for appenders to flush before canceling the background task.
+ ///
+ public Double ShutdownFlushTimeout
+ {
+ get
+ {
+ return _shutdownFlushTimeout;
+ }
+ set
+ {
+ _shutdownFlushTimeout = value;
+ }
+ }
+
+ protected override string InternalLoggerName
+ {
+ get { return "ParallelForwardingAppender"; }
+ }
+
+ #endregion Properties
+
+ #region Startup
+
+ public override void ActivateOptions()
+ {
+ base.ActivateOptions();
+ _shutdownFlushTimespan = TimeSpan.FromSeconds(_shutdownFlushTimeout);
+ StartForwarding();
+ }
+
+ private void StartForwarding()
+ {
+ if (_shutDownRequested)
+ {
+ return;
+ }
+ //Create a collection which will block the thread and wait for new entries
+ //if the collection is empty
+ if (BufferSize.HasValue && BufferSize > 0)
+ {
+ _loggingEvents = new BlockingCollection(BufferSize.Value);
+ }
+ else
+ {
+ //No limit on the number of events.
+ _loggingEvents = new BlockingCollection();
+ }
+ //The cancellation token is used to cancel a running task gracefully.
+ _loggingCancelationTokenSource = new CancellationTokenSource();
+ _loggingCancelationToken = _loggingCancelationTokenSource.Token;
+ _loggingTask = new Task(SubscriberLoop, _loggingCancelationToken);
+ _loggingTask.Start();
+ }
+
+ #endregion Startup
+
+ #region Shutdown
+
+ private void CompleteSubscriberTask()
+ {
+ _shutDownRequested = true;
+ if (_loggingEvents == null || _loggingEvents.IsAddingCompleted)
+ {
+ return;
+ }
+ //Don't allow more entries to be added.
+ _loggingEvents.CompleteAdding();
+ //Allow some time to flush
+ Thread.Sleep(_shutdownFlushTimespan);
+ if (!_loggingTask.IsCompleted && !_loggingCancelationToken.IsCancellationRequested)
+ {
+ _loggingCancelationTokenSource.Cancel();
+ //Wait here so that the error logging messages do not get into a random order.
+ //Don't pass the cancellation token because we are not interested
+ //in catching the OperationCanceledException that results.
+ _loggingTask.Wait();
+ }
+ if (!_loggingEvents.IsCompleted)
+ {
+ ForwardInternalError("The buffer was not able to be flushed before timeout occurred.", null, ThisType);
+ }
+ }
+
+ protected override void OnClose()
+ {
+ CompleteSubscriberTask();
+ base.OnClose();
+ }
+
+ #endregion Shutdown
+
+ #region Appending
+
+ protected override void Append(LoggingEvent loggingEvent)
+ {
+ if (_loggingEvents == null || _loggingEvents.IsAddingCompleted || loggingEvent == null)
+ {
+ return;
+ }
+
+ loggingEvent.Fix = Fix;
+ //In the case where blocking on a full collection, and the task is subsequently completed, the cancellation token
+ //will prevent the entry from attempting to add to the completed collection which would result in an exception.
+ _loggingEvents.Add(new LoggingEventContext(loggingEvent), _loggingCancelationToken);
+ }
+
+ protected override void Append(LoggingEvent[] loggingEvents)
+ {
+ if (_loggingEvents == null || _loggingEvents.IsAddingCompleted || loggingEvents == null)
+ {
+ return;
+ }
+
+ foreach (var loggingEvent in loggingEvents)
+ {
+ Append(loggingEvent);
+ }
+ }
+
+ #endregion Appending
+
+ #region Forwarding
+
+ ///
+ /// Iterates over a BlockingCollection containing LoggingEvents.
+ ///
+ private void SubscriberLoop()
+ {
+ Thread.CurrentThread.Name = String.Format("{0} ParallelForwardingAppender Subscriber Task", Name);
+ //The task will continue in a blocking loop until
+ //the queue is marked as adding completed, or the task is canceled.
+ try
+ {
+ //This call blocks until an item is available or until adding is completed
+ foreach (var entry in _loggingEvents.GetConsumingEnumerable(_loggingCancelationToken))
+ {
+ ForwardLoggingEvent(entry.LoggingEvent, ThisType);
+ }
+ }
+ catch (OperationCanceledException ex)
+ {
+ //The thread was canceled before all entries could be forwarded and the collection completed.
+ ForwardInternalError("Subscriber task was canceled before completion.", ex, ThisType);
+ //Cancellation is called in the CompleteSubscriberTask so don't call that again.
+ }
+ catch (ThreadAbortException ex)
+ {
+ //Thread abort may occur on domain unload.
+ ForwardInternalError("Subscriber task was aborted.", ex, ThisType);
+ //Cannot recover from a thread abort so complete the task.
+ CompleteSubscriberTask();
+ //The exception is swallowed because we don't want the client application
+ //to halt due to a logging issue.
+ }
+ catch (Exception ex)
+ {
+ //On exception, try to log the exception
+ ForwardInternalError("Subscriber task error in forwarding loop.", ex, ThisType);
+ //Any error in the loop is going to be some sort of extenuating circumstance from which we
+ //probably cannot recover anyway. Complete subscribing.
+ CompleteSubscriberTask();
+ }
+ }
+
+ #endregion Forwarding
+
+ #region IDisposable Implementation
+
+ private bool _disposed = false;
+
+ //Implement IDisposable.
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ if (_loggingTask != null)
+ {
+ if (!(_loggingTask.IsCanceled || _loggingTask.IsCompleted || _loggingTask.IsFaulted))
+ {
+ try
+ {
+ CompleteSubscriberTask();
+ }
+ catch (Exception ex)
+ {
+ LogLog.Error(ThisType, "Exception Completing Subscriber Task in Dispose Method", ex);
+ }
+ }
+ try
+ {
+ _loggingTask.Dispose();
+ }
+ catch (Exception ex)
+ {
+ LogLog.Error(ThisType, "Exception Disposing Logging Task", ex);
+ }
+ finally
+ {
+ _loggingTask = null;
+ }
+ }
+ if (_loggingEvents != null)
+ {
+ try
+ {
+ _loggingEvents.Dispose();
+ }
+ catch (Exception ex)
+ {
+ LogLog.Error(ThisType, "Exception Disposing BlockingCollection", ex);
+ }
+ finally
+ {
+ _loggingEvents = null;
+ }
+ }
+ if (_loggingCancelationTokenSource != null)
+ {
+ try
+ {
+ _loggingCancelationTokenSource.Dispose();
+ }
+ catch (Exception ex)
+ {
+ LogLog.Error(ThisType, "Exception Disposing CancellationTokenSource", ex);
+ }
+ finally
+ {
+ _loggingCancelationTokenSource = null;
+ }
+ }
+ }
+ _disposed = true;
+ }
+ }
+
+ // Use C# destructor syntax for finalization code.
+ ~ParallelForwardingAppender()
+ {
+ // Simply call Dispose(false).
+ Dispose(false);
+ }
+
+ #endregion IDisposable Implementation
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs
index e09c370053..7e59726c80 100644
--- a/src/Umbraco.Core/Models/DictionaryItem.cs
+++ b/src/Umbraco.Core/Models/DictionaryItem.cs
@@ -14,22 +14,22 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class DictionaryItem : Entity, IDictionaryItem
{
- private Guid _parentId;
+ private Guid? _parentId;
private string _itemKey;
private IEnumerable _translations;
public DictionaryItem(string itemKey)
- : this(new Guid(Constants.Conventions.Localization.DictionaryItemRootId), itemKey)
+ : this(null, itemKey)
{}
- public DictionaryItem(Guid parentId, string itemKey)
+ public DictionaryItem(Guid? parentId, string itemKey)
{
_parentId = parentId;
_itemKey = itemKey;
_translations = new List();
}
- private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId);
+ private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId);
private static readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey);
private static readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations);
@@ -37,7 +37,7 @@ namespace Umbraco.Core.Models
/// Gets or Sets the Parent Id of the Dictionary Item
///
[DataMember]
- public Guid ParentId
+ public Guid? ParentId
{
get { return _parentId; }
set
@@ -95,11 +95,7 @@ namespace Umbraco.Core.Models
{
base.AddingEntity();
- Key = Guid.NewGuid();
-
- //If ParentId is not set we should default to the root parent id
- if(ParentId == Guid.Empty)
- _parentId = new Guid(Constants.Conventions.Localization.DictionaryItemRootId);
+ Key = Guid.NewGuid();
}
}
diff --git a/src/Umbraco.Core/Models/IDictionaryItem.cs b/src/Umbraco.Core/Models/IDictionaryItem.cs
index e10a80e831..ed88acfbec 100644
--- a/src/Umbraco.Core/Models/IDictionaryItem.cs
+++ b/src/Umbraco.Core/Models/IDictionaryItem.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Models
/// Gets or Sets the Parent Id of the Dictionary Item
///
[DataMember]
- Guid ParentId { get; set; }
+ Guid? ParentId { get; set; }
///
/// Gets or sets the Key for the Dictionary Item
diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
index 1523cf9040..0d803c26e5 100644
--- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
+++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
@@ -12,6 +12,13 @@ namespace Umbraco.Core.Models.Identity
public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim>
{
+ public BackOfficeIdentityUser()
+ {
+ StartMediaId = -1;
+ StartContentId = -1;
+ Culture = Configuration.GlobalSettings.DefaultUILanguage;
+ }
+
public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager)
{
// NOTE the authenticationType must match the umbraco one
@@ -31,6 +38,30 @@ namespace Umbraco.Core.Models.Identity
public string UserTypeAlias { get; set; }
+ ///
+ /// Lockout is always enabled
+ ///
+ public override bool LockoutEnabled
+ {
+ get { return true; }
+ set
+ {
+ //do nothing
+ }
+ }
+
+ ///
+ /// Based on the user's lockout end date, this will determine if they are locked out
+ ///
+ internal bool IsLockedOut
+ {
+ get
+ {
+ var isLocked = (LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now);
+ return isLocked;
+ }
+ }
+
///
/// Overridden to make the retrieval lazy
///
diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
index def71a8982..f57d6683a2 100644
--- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
+++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
@@ -14,8 +14,7 @@ namespace Umbraco.Core.Models.Identity
config.CreateMap()
.ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email))
.ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id))
- .ForMember(user => user.LockoutEnabled, expression => expression.MapFrom(user => user.IsLockedOut))
- .ForMember(user => user.LockoutEndDateUtc, expression => expression.UseValue(DateTime.MaxValue.ToUniversalTime()))
+ .ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null))
.ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username))
.ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue)))
.ForMember(user => user.Culture, expression => expression.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService)))
@@ -23,6 +22,7 @@ namespace Umbraco.Core.Models.Identity
.ForMember(user => user.StartMediaId, expression => expression.MapFrom(user => user.StartMediaId))
.ForMember(user => user.StartContentId, expression => expression.MapFrom(user => user.StartContentId))
.ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias))
+ .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts))
.ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray()));
}
diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs
index fcc23ed858..a15639f886 100644
--- a/src/Umbraco.Core/Models/Macro.cs
+++ b/src/Umbraco.Core/Models/Macro.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Core.Models
///
[Serializable]
[DataContract(IsReference = true)]
- internal class Macro : Entity, IMacro
+ public class Macro : Entity, IMacro
{
public Macro()
{
diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs
index 94e46e1ed1..7976a7eac6 100644
--- a/src/Umbraco.Core/Models/PropertyGroup.cs
+++ b/src/Umbraco.Core/Models/PropertyGroup.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Specialized;
using System.Diagnostics;
+using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
@@ -105,6 +106,13 @@ namespace Umbraco.Core.Models
set
{
_propertyTypes = value;
+
+ //since we're adding this collection to this group, we need to ensure that all the lazy values are set.
+ foreach (var propertyType in _propertyTypes)
+ {
+ propertyType.PropertyGroupId = new Lazy(() => this.Id);
+ }
+
_propertyTypes.CollectionChanged += PropertyTypesChanged;
}
}
diff --git a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs b/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs
index 18fd16b658..712d1937a9 100644
--- a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs
+++ b/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs
@@ -19,7 +19,9 @@ namespace Umbraco.Core.Models.Rdbms
public Guid UniqueId { get; set; }
[Column("parent")]
- public Guid Parent { get; set; }
+ [NullSetting(NullSetting = NullSettings.Null)]
+ [ForeignKey(typeof(DictionaryDto), Column = "id")]
+ public Guid? Parent { get; set; }
[Column("key")]
[Length(1000)]
diff --git a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs b/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs
index d062df4eae..87329fbd4c 100644
--- a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs
+++ b/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs
@@ -14,6 +14,7 @@ namespace Umbraco.Core.Models.Rdbms
public int PrimaryKey { get; set; }
[Column("languageId")]
+ [ForeignKey(typeof(LanguageDto), Column = "id")]
public int LanguageId { get; set; }
[Column("UniqueId")]
diff --git a/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs b/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs
deleted file mode 100644
index 6826377856..0000000000
--- a/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using Umbraco.Core.Persistence;
-using Umbraco.Core.Persistence.DatabaseAnnotations;
-
-namespace Umbraco.Core.Models.Rdbms
-{
- [TableName("umbracoUserLogins")]
- [ExplicitColumns]
- internal class UserLoginDto
- {
- [Column("contextID")]
- [Index(IndexTypes.Clustered, Name = "IX_umbracoUserLogins_Index")]
- public Guid ContextId { get; set; }
-
- [Column("userID")]
- public int UserId { get; set; }
-
- [Column("timeout")]
- public long Timeout { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs
index 87eb06e295..0b478d54cf 100644
--- a/src/Umbraco.Core/ObjectResolution/Resolution.cs
+++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs
@@ -112,7 +112,7 @@ namespace Umbraco.Core.ObjectResolution
/// resolution is already frozen.
public static void Freeze()
{
- LogHelper.Debug(typeof(Resolution), "Freezing resolution");
+ LogHelper.Debug(typeof (Resolution), "Freezing resolution");
using (new WriteLock(ConfigurationLock))
{
@@ -121,9 +121,20 @@ namespace Umbraco.Core.ObjectResolution
_isFrozen = true;
}
-
- if (Frozen != null)
- Frozen(null, null);
+
+ LogHelper.Debug(typeof(Resolution), "Resolution is frozen");
+
+ if (Frozen == null) return;
+
+ try
+ {
+ Frozen(null, null);
+ }
+ catch (Exception e)
+ {
+ LogHelper.Error(typeof (Resolution), "Exception in Frozen event handler.", e);
+ throw;
+ }
}
///
diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs
index ca64775880..55c1fe505f 100644
--- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs
+++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs
@@ -151,6 +151,13 @@ namespace Umbraco.Core.Persistence
_db.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" });
}
+ //Loop through index statements and execute sql
+ foreach (var sql in indexSql)
+ {
+ int createdIndex = _db.Execute(new Sql(sql));
+ _logger.Info(string.Format("Create Index sql {0}:\n {1}", createdIndex, sql));
+ }
+
//Loop through foreignkey statements and execute sql
foreach (var sql in foreignSql)
{
@@ -158,12 +165,7 @@ namespace Umbraco.Core.Persistence
_logger.Info(string.Format("Create Foreign Key sql {0}:\n {1}", createdFk, sql));
}
- //Loop through index statements and execute sql
- foreach (var sql in indexSql)
- {
- int createdIndex = _db.Execute(new Sql(sql));
- _logger.Info(string.Format("Create Index sql {0}:\n {1}", createdIndex, sql));
- }
+
transaction.Complete();
}
diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs
index ce2a53ea95..0ba269a3ba 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs
@@ -48,8 +48,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{7, typeof (DataTypeDto)},
{8, typeof (DataTypePreValueDto)},
{9, typeof (DictionaryDto)},
- {10, typeof (LanguageTextDto)},
- {11, typeof (LanguageDto)},
+ {10, typeof (LanguageDto)},
+ {11, typeof (LanguageTextDto)},
{12, typeof (DomainDto)},
{13, typeof (LogDto)},
{14, typeof (MacroDto)},
@@ -68,7 +68,6 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{27, typeof (StylesheetPropertyDto)},
{28, typeof (TagDto)},
{29, typeof (TagRelationshipDto)},
- {30, typeof (UserLoginDto)},
{31, typeof (UserTypeDto)},
{32, typeof (UserDto)},
{33, typeof (TaskTypeDto)},
diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs
index e7535e78d4..7dde31d3e3 100644
--- a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs
@@ -53,5 +53,32 @@ namespace Umbraco.Core.Persistence.Migrations
/// to ensure they are not executed twice.
///
internal string Name { get; set; }
+
+ protected string GetQuotedValue(object val)
+ {
+ if (val == null) return "NULL";
+
+ var type = val.GetType();
+
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.Boolean:
+ return ((bool)val) ? "1" : "0";
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ case TypeCode.SByte:
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ case TypeCode.Byte:
+ case TypeCode.UInt16:
+ case TypeCode.UInt32:
+ case TypeCode.UInt64:
+ return val.ToString();
+ default:
+ return SqlSyntaxContext.SqlSyntaxProvider.GetQuotedValue(val.ToString());
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs
index 2131a7b106..73966f6e93 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs
@@ -44,7 +44,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions
whereClauses.Add(string.Format("{0} {1} {2}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(item.Key),
item.Value == null ? "IS" : "=",
- SqlSyntaxContext.SqlSyntaxProvider.GetQuotedValue(item.Value.ToString())));
+ GetQuotedValue(item.Value)));
}
deleteItems.Add(string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteData,
diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs
index 330e22bb93..568087733b 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs
@@ -60,29 +60,6 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert.Expressions
return string.Join(",", insertItems);
}
- private string GetQuotedValue(object val)
- {
- var type = val.GetType();
-
- switch (Type.GetTypeCode(type))
- {
- case TypeCode.Boolean:
- return ((bool) val) ? "1" : "0";
- case TypeCode.Single:
- case TypeCode.Double:
- case TypeCode.Decimal:
- case TypeCode.SByte:
- case TypeCode.Int16:
- case TypeCode.Int32:
- case TypeCode.Int64:
- case TypeCode.Byte:
- case TypeCode.UInt16:
- case TypeCode.UInt32:
- case TypeCode.UInt64:
- return val.ToString();
- default:
- return SqlSyntaxContext.SqlSyntaxProvider.GetQuotedValue(val.ToString());
- }
- }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs
index a5a3204974..fd470dd7f2 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions
{
updateItems.Add(string.Format("{0} = {1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(item.Key),
- SqlSyntaxContext.SqlSyntaxProvider.GetQuotedValue(item.Value.ToString())));
+ GetQuotedValue(item.Value)));
}
if (IsAllRows)
@@ -46,7 +46,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions
whereClauses.Add(string.Format("{0} {1} {2}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(item.Key),
item.Value == null ? "IS" : "=",
- SqlSyntaxContext.SqlSyntaxProvider.GetQuotedValue(item.Value.ToString())));
+ GetQuotedValue(item.Value)));
}
}
return string.Format(SqlSyntaxContext.SqlSyntaxProvider.UpdateData,
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs
index f6b7e75189..d822b7593a 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs
@@ -38,15 +38,20 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven
if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql)
{
Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("nodeId").ToTable("umbracoNode").PrimaryColumn("id");
+ //check for another strange really old one that might have existed
+ if (constraints.Any(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "tagId"))
+ {
+ Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("tagId").ToTable("cmsTags").PrimaryColumn("id");
+ }
}
else
{
//Before we try to delete this constraint, we'll see if it exists first, some older schemas never had it and some older schema's had this named
// differently than the default.
- var constraint = constraints
- .SingleOrDefault(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false);
- if (constraint != null)
+ var constraintMatches = constraints.Where(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false);
+
+ foreach (var constraint in constraintMatches)
{
Delete.ForeignKey(constraint.Item3).OnTable("cmsTagRelationship");
}
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs
new file mode 100644
index 0000000000..81b93a8883
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Data;
+using System.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Persistence.SqlSyntax;
+
+namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero
+{
+ [Migration("7.3.0", 14, GlobalSettings.UmbracoMigrationName)]
+ public class AddForeignKeysForLanguageAndDictionaryTables : MigrationBase
+ {
+ public AddForeignKeysForLanguageAndDictionaryTables(ISqlSyntaxProvider sqlSyntax, ILogger logger)
+ : base(sqlSyntax, logger)
+ {
+ }
+
+ public override void Up()
+ {
+ var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray();
+
+ //if the FK doesn't exist
+ if (constraints.Any(x => x.Item1.InvariantEquals("cmsLanguageText") && x.Item2.InvariantEquals("languageId") && x.Item3.InvariantEquals("FK_cmsLanguageText_umbracoLanguage_id")) == false)
+ {
+ //Somehow, a language text item might end up with a language Id of zero or one that no longer exists
+ //before we add the foreign key
+ foreach (var pk in Context.Database.Query(
+ "SELECT cmsLanguageText.pk FROM cmsLanguageText WHERE cmsLanguageText.languageId NOT IN (SELECT umbracoLanguage.id FROM umbracoLanguage)"))
+ {
+ Delete.FromTable("cmsLanguageText").Row(new { pk = pk });
+ }
+
+ //now we need to create a foreign key
+ Create.ForeignKey("FK_cmsLanguageText_umbracoLanguage_id").FromTable("cmsLanguageText").ForeignColumn("languageId")
+ .ToTable("umbracoLanguage").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None);
+
+ Alter.Table("cmsDictionary").AlterColumn("parent").AsGuid().Nullable();
+
+ //set the parent to null if it equals the default dictionary item root id
+ foreach (var pk in Context.Database.Query("SELECT pk FROM cmsDictionary WHERE parent NOT IN (SELECT id FROM cmsDictionary)"))
+ {
+ Update.Table("cmsDictionary").Set(new { parent = (Guid?)null }).Where(new { pk = pk });
+ }
+
+ Create.ForeignKey("FK_cmsDictionary_cmsDictionary_id").FromTable("cmsDictionary").ForeignColumn("parent")
+ .ToTable("cmsDictionary").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None);
+ }
+
+
+
+ }
+
+ public override void Down()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs
index 7dd5f81b3a..d60385926b 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs
@@ -30,6 +30,9 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
}
var xmlFile = IOHelper.MapPath(SystemFiles.AccessXml);
+
+ if (File.Exists(xmlFile) == false) return;
+
using (var fileStream = File.OpenRead(xmlFile))
{
var xml = XDocument.Load(fileStream);
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs
new file mode 100644
index 0000000000..b842ec041a
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs
@@ -0,0 +1,29 @@
+using System.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Persistence.SqlSyntax;
+
+namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero
+{
+ [Migration("7.3.0", 15, GlobalSettings.UmbracoMigrationName)]
+ public class RemoveUmbracoLoginsTable : MigrationBase
+ {
+ public RemoveUmbracoLoginsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger)
+ : base(sqlSyntax, logger)
+ {
+ }
+
+ public override void Up()
+ {
+ if (SqlSyntax.GetColumnsInSchema(Context.Database).Any(x => x.TableName.InvariantEquals("umbracoUserLogins")))
+ {
+ Delete.Table("umbracoUserLogins");
+ }
+ }
+
+ public override void Down()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs
index 67741c84d7..b368488833 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs
@@ -83,7 +83,7 @@ namespace Umbraco.Core.Persistence.Repositories
NodeId = entity.Id,
Timestamp = DateTime.Now,
VersionId = entity.Version,
- Xml = entity.Xml.ToString(SaveOptions.None)
+ Xml = entity.Xml.ToDataString()
};
//We need to do a special InsertOrUpdate here because we know that the PreviewXmlDto table has a composite key and thus
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index 1bec751c6c..3395b38b3d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -20,6 +20,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Cache;
+using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
@@ -37,8 +38,8 @@ namespace Umbraco.Core.Persistence.Repositories
private readonly ContentPreviewRepository _contentPreviewRepository;
private readonly ContentXmlRepository _contentXmlRepository;
- public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository)
- : base(work, cacheHelper, logger, syntaxProvider)
+ public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection)
+ : base(work, cacheHelper, logger, syntaxProvider, contentSection)
{
if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository");
if (templateRepository == null) throw new ArgumentNullException("templateRepository");
@@ -237,7 +238,7 @@ namespace Umbraco.Core.Persistence.Repositories
var xmlItems = (from descendant in descendants
let xml = serializer(descendant)
- select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToString(SaveOptions.None) }).ToArray();
+ select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
//bulk insert it into the database
Database.BulkInsertRecords(xmlItems, tr);
@@ -351,11 +352,10 @@ namespace Umbraco.Core.Persistence.Repositories
//NOTE Should the logic below have some kind of fallback for empty parent ids ?
//Logic for setting Path, Level and SortOrder
var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId });
- int level = parent.Level + 1;
- var maxSortOrder =
- Database.ExecuteScalar(
- "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType",
- new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId });
+ var level = parent.Level + 1;
+ var maxSortOrder = Database.ExecuteScalar(
+ "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType",
+ new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId });
var sortOrder = maxSortOrder + 1;
//Create the (base) node data - umbracoNode
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
index 951475c358..fd4577faa5 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
@@ -24,15 +24,18 @@ namespace Umbraco.Core.Persistence.Repositories
///
/// Exposes shared functionality
///
- internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase
+ internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase, IReadRepository
where TEntity : class, IContentTypeComposition
{
protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
+ _guidRepo = new GuidReadOnlyContentTypeBaseRepository(this, work, cache, logger, sqlSyntax);
}
+ private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo;
+
///
/// The container object type - used for organizing content types
///
@@ -119,7 +122,7 @@ namespace Umbraco.Core.Persistence.Repositories
yield return dto.ContentTypeNodeId;
}
}
-
+
protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, DataTypeDatabaseType dbType, string propertyTypeAlias)
{
return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias);
@@ -178,7 +181,7 @@ AND umbracoNode.nodeObjectType = @objectType",
else
{
//Fallback for ContentTypes with no identity
- var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new {Alias = composition.Alias});
+ var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new { Alias = composition.Alias });
if (contentTypeDto != null)
{
Database.Insert(new ContentType2ContentTypeDto { ParentId = contentTypeDto.NodeId, ChildId = entity.Id });
@@ -198,7 +201,7 @@ AND umbracoNode.nodeObjectType = @objectType",
}
var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId);
-
+
//Insert Tabs
foreach (var propertyGroup in entity.PropertyGroups)
{
@@ -259,16 +262,16 @@ AND umbracoNode.id <> @id",
var o = Database.Update(nodeDto);
//Look up ContentType entry to get PrimaryKey for updating the DTO
- var dtoPk = Database.First("WHERE nodeId = @Id", new {Id = entity.Id});
+ var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id });
dto.PrimaryKey = dtoPk.PrimaryKey;
Database.Update(dto);
//Delete the ContentType composition entries before adding the updated collection
- Database.Delete("WHERE childContentTypeId = @Id", new {Id = entity.Id});
+ Database.Delete("WHERE childContentTypeId = @Id", new { Id = entity.Id });
//Update ContentType composition in new table
foreach (var composition in entity.ContentTypeComposition)
{
- Database.Insert(new ContentType2ContentTypeDto {ParentId = composition.Id, ChildId = entity.Id});
+ Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id });
}
//Removing a ContentType from a composition (U4-1690)
@@ -293,7 +296,7 @@ AND umbracoNode.id <> @id",
foreach (var key in compositionBase.RemovedContentTypeKeyTracker)
{
//Find PropertyTypes for the removed ContentType
- var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new {Id = key});
+ var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key });
//Loop through the Content that is based on the current ContentType in order to remove the Properties that are
//based on the PropertyTypes that belong to the removed ContentType.
foreach (var contentDto in contentDtos)
@@ -318,7 +321,7 @@ AND umbracoNode.id <> @id",
}
//Delete the allowed content type entries before adding the updated collection
- Database.Delete("WHERE Id = @Id", new {Id = entity.Id});
+ Database.Delete("WHERE Id = @Id", new { Id = entity.Id });
//Insert collection of allowed content types
foreach (var allowedContentType in entity.AllowedContentTypes)
{
@@ -331,10 +334,10 @@ AND umbracoNode.id <> @id",
}
- if (((ICanBeDirty) entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty()))
+ if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty()))
{
//Delete PropertyTypes by excepting entries from db with entries from collections
- var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new {Id = entity.Id});
+ var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id });
var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id);
var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id);
var items = dbPropertyTypeAlias.Except(entityPropertyTypes);
@@ -342,18 +345,18 @@ AND umbracoNode.id <> @id",
{
//Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted.
Database.Delete("WHERE propertyTypeId = @Id", new { Id = item });
- Database.Delete("WHERE propertytypeid = @Id", new {Id = item});
+ Database.Delete("WHERE propertytypeid = @Id", new { Id = item });
Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId",
- new {Id = entity.Id, PropertyTypeId = item});
+ new { Id = entity.Id, PropertyTypeId = item });
}
}
- if (entity.IsPropertyDirty("PropertyGroups") ||
+ if (entity.IsPropertyDirty("PropertyGroups") ||
entity.PropertyGroups.Any(x => x.IsDirty()))
{
//Delete Tabs/Groups by excepting entries from db with entries from collections
var dbPropertyGroups =
- Database.Fetch("WHERE contenttypeNodeId = @Id", new {Id = entity.Id})
+ Database.Fetch("WHERE contenttypeNodeId = @Id", new { Id = entity.Id })
.Select(x => new Tuple(x.Id, x.Text))
.ToList();
var entityPropertyGroups = entity.PropertyGroups.Select(x => new Tuple(x.Id, x.Name)).ToList();
@@ -389,11 +392,11 @@ AND umbracoNode.id <> @id",
foreach (var tab in tabs)
{
Database.Update("SET propertyTypeGroupId = NULL WHERE propertyTypeGroupId = @PropertyGroupId",
- new {PropertyGroupId = tab.Item1});
+ new { PropertyGroupId = tab.Item1 });
Database.Update("SET parentGroupId = NULL WHERE parentGroupId = @TabId",
- new {TabId = tab.Item1});
+ new { TabId = tab.Item1 });
Database.Delete("WHERE contenttypeNodeId = @Id AND text = @Name",
- new {Id = entity.Id, Name = tab.Item2});
+ new { Id = entity.Id, Name = tab.Item2 });
}
}
@@ -507,7 +510,7 @@ AND umbracoNode.id <> @id",
var list = new List();
foreach (var dto in dtos.Where(x => (x.PropertyTypeGroupId > 0) == false))
{
- var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias);
+ var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias);
propType.DataTypeDefinitionId = dto.DataTypeId;
propType.Description = dto.Description;
propType.Id = dto.Id;
@@ -558,7 +561,7 @@ AND umbracoNode.id <> @id",
throw exception;
});
}
-
+
///
/// Try to set the data type id based on its ControlId
///
@@ -621,28 +624,30 @@ AND umbracoNode.id <> @id",
}
}
- public static IEnumerable GetMediaTypes(
- int[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
+ public static IEnumerable GetMediaTypes(
+ TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
TRepo contentTypeRepository)
- where TRepo : IRepositoryQueryable
+ where TRepo : IReadRepository
+ where TId: struct
{
- IDictionary> allParentMediaTypeIds;
+ IDictionary> allParentMediaTypeIds;
var mediaTypes = MapMediaTypes(mediaTypeIds, db, sqlSyntax, out allParentMediaTypeIds)
.ToArray();
MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds);
-
+
return mediaTypes;
}
- public static IEnumerable GetContentTypes(
- int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
+ public static IEnumerable GetContentTypes(
+ TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
TRepo contentTypeRepository,
ITemplateRepository templateRepository)
- where TRepo : IRepositoryQueryable
+ where TRepo : IReadRepository
+ where TId : struct
{
- IDictionary> allAssociatedTemplates;
- IDictionary> allParentContentTypeIds;
+ IDictionary> allAssociatedTemplates;
+ IDictionary> allParentContentTypeIds;
var contentTypes = MapContentTypes(contentTypeIds, db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds)
.ToArray();
@@ -652,17 +657,18 @@ AND umbracoNode.id <> @id",
contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates);
MapContentTypeChildren(
- contentTypes, db, sqlSyntax, contentTypeRepository, allParentContentTypeIds);
+ contentTypes, db, sqlSyntax, contentTypeRepository, allParentContentTypeIds);
}
return contentTypes;
}
- internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes,
+ internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes,
Database db, ISqlSyntaxProvider sqlSyntax,
TRepo contentTypeRepository,
- IDictionary> allParentContentTypeIds)
- where TRepo : IRepositoryQueryable
+ IDictionary> allParentContentTypeIds)
+ where TRepo : IReadRepository
+ where TId : struct
{
//NOTE: SQL call #2
@@ -674,9 +680,9 @@ AND umbracoNode.id <> @id",
foreach (var contentType in contentTypes)
{
contentType.PropertyGroups = allPropGroups[contentType.Id];
- ((ContentTypeBase) contentType).PropertyTypes = allPropTypes[contentType.Id];
+ ((ContentTypeBase)contentType).PropertyTypes = allPropTypes[contentType.Id];
}
-
+
//NOTE: SQL call #3++
if (allParentContentTypeIds != null)
@@ -687,7 +693,18 @@ AND umbracoNode.id <> @id",
var allParentContentTypes = contentTypeRepository.GetAll(allParentIdsAsArray).ToArray();
foreach (var contentType in contentTypes)
{
- var parentContentTypes = allParentContentTypes.Where(x => allParentContentTypeIds[contentType.Id].Contains(x.Id));
+ //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids
+ // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be
+ var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key;
+
+ var parentContentTypes = allParentContentTypes.Where(x =>
+ {
+ //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids
+ // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be
+ var parentEntityId = typeof(TId) == typeof(int) ? x.Id : (object)x.Key;
+
+ return allParentContentTypeIds[(TId)entityId].Contains((TId)parentEntityId);
+ });
foreach (var parentContentType in parentContentTypes)
{
var result = contentType.AddContentType(parentContentType);
@@ -701,15 +718,16 @@ AND umbracoNode.id <> @id",
}
}
-
+
}
- internal static void MapContentTypeTemplates(IContentType[] contentTypes,
+ internal static void MapContentTypeTemplates(IContentType[] contentTypes,
Database db,
TRepo contentTypeRepository,
ITemplateRepository templateRepository,
- IDictionary> associatedTemplates)
- where TRepo : IRepositoryQueryable
+ IDictionary> associatedTemplates)
+ where TRepo : IReadRepository
+ where TId: struct
{
if (associatedTemplates == null || associatedTemplates.Any() == false) return;
@@ -726,7 +744,11 @@ AND umbracoNode.id <> @id",
foreach (var contentType in contentTypes)
{
- var associatedTemplateIds = associatedTemplates[contentType.Id].Select(x => x.TemplateId)
+ //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids
+ // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be
+ var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key;
+
+ var associatedTemplateIds = associatedTemplates[(TId)entityId].Select(x => x.TemplateId)
.Distinct()
.ToArray();
@@ -735,11 +757,12 @@ AND umbracoNode.id <> @id",
: Enumerable.Empty()).ToArray();
}
-
+
}
- internal static IEnumerable MapMediaTypes(int[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
- out IDictionary> parentMediaTypeIds)
+ internal static IEnumerable MapMediaTypes(TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
+ out IDictionary> parentMediaTypeIds)
+ where TId : struct
{
Mandate.That(mediaTypeIds.Any(), () => new InvalidOperationException("must be at least one content type id specified"));
Mandate.ParameterNotNull(db, "db");
@@ -766,8 +789,21 @@ AND umbracoNode.id <> @id",
ON AllowedTypes.Id = cmsContentType.nodeId
LEFT JOIN cmsContentType2ContentType as ParentTypes
ON ParentTypes.childContentTypeId = cmsContentType.nodeId
- WHERE (umbracoNode.nodeObjectType = @nodeObjectType)
- AND (umbracoNode.id IN (@contentTypeIds))";
+ WHERE (umbracoNode.nodeObjectType = @nodeObjectType)";
+
+ if (mediaTypeIds.Any())
+ {
+ //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will
+ // work for the time being.
+ if (typeof(TId) == typeof(int))
+ {
+ sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))";
+ }
+ else if (typeof(TId) == typeof(Guid))
+ {
+ sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))";
+ }
+ }
//NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count!
if ((mediaTypeIds.Length - 1) > 2000)
@@ -781,7 +817,7 @@ AND umbracoNode.id <> @id",
return Enumerable.Empty();
}
- parentMediaTypeIds = new Dictionary>();
+ parentMediaTypeIds = new Dictionary>();
var mappedMediaTypes = new List();
foreach (var contentTypeId in mediaTypeIds)
@@ -793,7 +829,14 @@ AND umbracoNode.id <> @id",
//first we want to get the main content type data this is 1 : 1 with umbraco node data
var ct = result
- .Where(x => x.ctId == currentCtId)
+ .Where(x =>
+ {
+ //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is
+ // how it is for now.
+ return (typeof (TId) == typeof (int))
+ ? x.ctId == currentCtId
+ : x.nUniqueId == currentCtId;
+ })
.Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId })
.DistinctBy(x => (int)x.ctId)
.FirstOrDefault();
@@ -817,7 +860,7 @@ AND umbracoNode.id <> @id",
NodeDto = new NodeDto
{
CreateDate = ct.nCreateDate,
- Level = (short) ct.nLevel,
+ Level = (short)ct.nLevel,
NodeId = ct.ctId,
NodeObjectType = ct.nObjectType,
ParentId = ct.nParentId,
@@ -829,7 +872,7 @@ AND umbracoNode.id <> @id",
UserId = ct.nUser
}
};
-
+
//now create the media type object
var factory = new MediaTypeFactory(new Guid(Constants.ObjectTypes.MediaType));
@@ -845,9 +888,10 @@ AND umbracoNode.id <> @id",
return mappedMediaTypes;
}
- internal static IEnumerable MapContentTypes(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
- out IDictionary> associatedTemplates,
- out IDictionary> parentContentTypeIds)
+ internal static IEnumerable MapContentTypes(TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
+ out IDictionary> associatedTemplates,
+ out IDictionary> parentContentTypeIds)
+ where TId : struct
{
Mandate.ParameterNotNull(db, "db");
@@ -884,8 +928,21 @@ AND umbracoNode.id <> @id",
LEFT JOIN cmsContentType2ContentType as ParentTypes
ON ParentTypes.childContentTypeId = cmsContentType.nodeId
WHERE (umbracoNode.nodeObjectType = @nodeObjectType)";
- if(contentTypeIds.Any())
- sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))";
+
+ if (contentTypeIds.Any())
+ {
+ //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will
+ // work for the time being.
+ if (typeof(TId) == typeof(int))
+ {
+ sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))";
+ }
+ else if (typeof(TId) == typeof(Guid))
+ {
+ sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))";
+ }
+ }
+
//NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count!
if ((contentTypeIds.Length - 1) > 2000)
@@ -900,8 +957,8 @@ AND umbracoNode.id <> @id",
return Enumerable.Empty();
}
- parentContentTypeIds = new Dictionary>();
- associatedTemplates = new Dictionary>();
+ parentContentTypeIds = new Dictionary>();
+ associatedTemplates = new Dictionary>();
var mappedContentTypes = new List();
foreach (var contentTypeId in contentTypeIds)
@@ -913,7 +970,14 @@ AND umbracoNode.id <> @id",
//first we want to get the main content type data this is 1 : 1 with umbraco node data
var ct = result
- .Where(x => (int)x.ctId == currentCtId)
+ .Where(x =>
+ {
+ //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is
+ // how it is for now.
+ return (typeof(TId) == typeof(int))
+ ? x.ctId == currentCtId
+ : x.nUniqueId == currentCtId;
+ })
.Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId })
.DistinctBy(x => (int)x.ctId)
.FirstOrDefault();
@@ -925,7 +989,14 @@ AND umbracoNode.id <> @id",
//get the unique list of associated templates
var defaultTemplates = result
- .Where(x => x.ctId == currentCtId)
+ .Where(x =>
+ {
+ //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is
+ // how it is for now.
+ return (typeof(TId) == typeof(int))
+ ? x.ctId == currentCtId
+ : x.nUniqueId == currentCtId;
+ })
//use a tuple so that distinct checks both values (in some rare cases the dtIsDefault will not compute as bool?, so we force it with Convert.ToBoolean)
.Select(x => new Tuple(Convert.ToBoolean(x.dtIsDefault), x.dtTemplateId))
.Where(x => x.Item1.HasValue && x.Item2.HasValue)
@@ -973,7 +1044,14 @@ AND umbracoNode.id <> @id",
// We will map a subset of the associated template - alias, id, name
associatedTemplates.Add(currentCtId, result
- .Where(x => x.ctId == currentCtId)
+ .Where(x =>
+ {
+ //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is
+ // how it is for now.
+ return (typeof(TId) == typeof(int))
+ ? x.ctId == currentCtId
+ : x.nUniqueId == currentCtId;
+ })
.Where(x => x.tId != null)
.Select(x => new AssociatedTemplate(x.tId, x.tAlias, x.tText))
.Distinct()
@@ -987,19 +1065,27 @@ AND umbracoNode.id <> @id",
//map the allowed content types
//map the child content type ids
MapCommonContentTypeObjects(contentType, currentCtId, result, parentContentTypeIds);
-
+
mappedContentTypes.Add(contentType);
}
return mappedContentTypes;
}
- private static void MapCommonContentTypeObjects(T contentType, int currentCtId, List result, IDictionary> parentContentTypeIds)
- where T: IContentTypeBase
+ private static void MapCommonContentTypeObjects(T contentType, TId currentCtId, List result, IDictionary> parentContentTypeIds)
+ where T : IContentTypeBase
+ where TId : struct
{
//map the allowed content types
contentType.AllowedContentTypes = result
- .Where(x => x.ctId == currentCtId)
+ .Where(x =>
+ {
+ //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is
+ // how it is for now.
+ return (typeof(TId) == typeof(int))
+ ? x.ctId == currentCtId
+ : x.nUniqueId == currentCtId;
+ })
//use tuple so we can use distinct on all vals
.Select(x => new Tuple(x.ctaAllowedId, x.ctaSortOrder, x.ctaAlias))
.Where(x => x.Item1.HasValue && x.Item2.HasValue && x.Item3 != null)
@@ -1009,8 +1095,15 @@ AND umbracoNode.id <> @id",
//map the child content type ids
parentContentTypeIds.Add(currentCtId, result
- .Where(x => x.ctId == currentCtId)
- .Select(x => (int?)x.chtParentId)
+ .Where(x =>
+ {
+ //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is
+ // how it is for now.
+ return (typeof(TId) == typeof(int))
+ ? x.ctId == currentCtId
+ : x.nUniqueId == currentCtId;
+ })
+ .Select(x => (TId?)x.chtParentId)
.Where(x => x.HasValue)
.Distinct()
.Select(x => x.Value).ToList());
@@ -1019,7 +1112,7 @@ AND umbracoNode.id <> @id",
internal static void MapGroupsAndProperties(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
out IDictionary allPropertyTypeCollection,
out IDictionary allPropertyGroupCollection)
- {
+ {
// first part Gets all property groups including property type data even when no property type exists on the group
// second part Gets all property types including ones that are not on a group
@@ -1057,7 +1150,7 @@ AND umbracoNode.id <> @id",
LEFT JOIN cmsPropertyTypeGroup as PG
ON PG.id = PT.propertyTypeGroupId");
- if(contentTypeIds.Any())
+ if (contentTypeIds.Any())
sqlBuilder.AppendLine(" WHERE (PT.contentTypeId in (@contentTypeIds))");
sqlBuilder.AppendLine(" ORDER BY (pgId)");
@@ -1082,7 +1175,7 @@ AND umbracoNode.id <> @id",
int currId = contentTypeId;
- var propertyGroupCollection = new PropertyGroupCollection(result
+ var propertyGroupCollection = new PropertyGroupCollection(result
//get all rows that have a group id
.Where(x => x.pgId != null)
//filter based on the current content type
@@ -1120,7 +1213,7 @@ AND umbracoNode.id <> @id",
//Create the property type collection now (that don't have groups)
- var propertyTypeCollection = new PropertyTypeCollection(result
+ var propertyTypeCollection = new PropertyTypeCollection(result
.Where(x => x.pgId == null)
//filter based on the current content type
.Where(x => x.contentTypeId == currId)
@@ -1141,9 +1234,105 @@ AND umbracoNode.id <> @id",
allPropertyTypeCollection[currId] = propertyTypeCollection;
}
-
+
}
}
+
+ ///
+ /// Inner repository to support the GUID lookups and keep the caching consistent
+ ///
+ internal class GuidReadOnlyContentTypeBaseRepository : PetaPocoRepositoryBase
+ {
+ private readonly ContentTypeBaseRepository _parentRepo;
+
+ public GuidReadOnlyContentTypeBaseRepository(
+ ContentTypeBaseRepository parentRepo,
+ IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
+ : base(work, cache, logger, sqlSyntax)
+ {
+ _parentRepo = parentRepo;
+ }
+
+ protected override TEntity PerformGet(Guid id)
+ {
+ return _parentRepo.PerformGet(id);
+ }
+
+ protected override IEnumerable PerformGetAll(params Guid[] ids)
+ {
+ return _parentRepo.PerformGetAll(ids);
+ }
+
+ protected override Sql GetBaseQuery(bool isCount)
+ {
+ return _parentRepo.GetBaseQuery(isCount);
+ }
+
+ protected override string GetBaseWhereClause()
+ {
+ return "umbracoNode.uniqueID = @Id";
+ }
+
+ #region No implementation required
+ protected override IEnumerable PerformGetByQuery(IQuery query)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable GetDeleteClauses()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override Guid NodeObjectTypeId
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ protected override void PersistNewItem(TEntity entity)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void PersistUpdatedItem(TEntity entity)
+ {
+ throw new NotImplementedException();
+ }
+ #endregion
+ }
+
+ protected abstract TEntity PerformGet(Guid id);
+ protected abstract IEnumerable PerformGetAll(params Guid[] ids);
+
+ ///
+ /// Gets an Entity by Id
+ ///
+ ///
+ ///
+ public TEntity Get(Guid id)
+ {
+ return _guidRepo.Get(id);
+ }
+
+ ///
+ /// Gets all entities of the spefified type
+ ///
+ ///
+ ///
+ public IEnumerable GetAll(params Guid[] ids)
+ {
+ return _guidRepo.GetAll(ids);
+ }
+
+ ///
+ /// Boolean indicating whether an Entity with the specified Id exists
+ ///
+ ///
+ ///
+ public bool Exists(Guid id)
+ {
+ return _guidRepo.Exists(id);
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs
index 4db21a5616..ab22b66679 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs
@@ -249,5 +249,28 @@ namespace Umbraco.Core.Persistence.Repositories
{
get { return new Guid(Constants.ObjectTypes.DocumentTypeContainer); }
}
+
+ protected override IContentType PerformGet(Guid id)
+ {
+ var contentTypes = ContentTypeQueryMapper.GetContentTypes(
+ new[] { id }, Database, SqlSyntax, this, _templateRepository);
+
+ var contentType = contentTypes.SingleOrDefault();
+ return contentType;
+ }
+
+ protected override IEnumerable PerformGetAll(params Guid[] ids)
+ {
+ if (ids.Any())
+ {
+ return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository);
+ }
+ else
+ {
+ var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId);
+ var allIds = Database.Fetch(sql).ToArray();
+ return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
index a9d5850368..b242bb34f7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
@@ -81,8 +81,8 @@ namespace Umbraco.Core.Persistence.Repositories
var poco = new ContentXmlDto
{
- NodeId = entity.Id,
- Xml = entity.Xml.ToString(SaveOptions.None)
+ NodeId = entity.Id,
+ Xml = entity.Xml.ToDataString()
};
//We need to do a special InsertOrUpdate here because we know that the ContentXmlDto table has a 1:1 relation
diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
index 30b92b2ddc..859c4e7ae7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
@@ -279,19 +280,22 @@ AND umbracoNode.id <> @id",
return GetAndCachePreValueCollection(dataTypeId);
}
+ internal static string GetCacheKeyRegex(int preValueId)
+ {
+ return CacheKeys.DataTypePreValuesCacheKey + @"[-\d]+-([\d]*,)*" + preValueId + @"(?!\d)[,\d$]*";
+ }
+
public string GetPreValueAsString(int preValueId)
{
//We need to see if we can find the cached PreValueCollection based on the cache key above
- var regex = CacheKeys.DataTypePreValuesCacheKey + @"[\d]+-[,\d]*" + preValueId + @"[,\d$]*";
-
- var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(regex);
+ var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId));
if (cached != null && cached.Any())
{
//return from the cache
var collection = cached.First();
- var preVal = collection.FormatAsDictionary().Single(x => x.Value.Id == preValueId);
- return preVal.Value.Value;
+ var preVal = collection.FormatAsDictionary().Single(x => x.Value.Id == preValueId);
+ return preVal.Value.Value;
}
//go and find the data type id for the pre val id passed in
diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
index cf3193943b..1e84814d1b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
@@ -103,12 +103,9 @@ namespace Umbraco.Core.Persistence.Repositories
return new List();
}
- ///
- /// Returns the Top Level Parent Guid Id
- ///
protected override Guid NodeObjectTypeId
{
- get { return new Guid(Constants.Conventions.Localization.DictionaryItemRootId); }
+ get { throw new NotImplementedException(); }
}
#endregion
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
index c27dd96d46..9d3fcbb40b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
@@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
- public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository
+ public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository
{
///
/// Get the count of published items
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs
index b612a9109d..245552e422 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs
@@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
- public interface IContentTypeRepository : IRepositoryQueryable
+ public interface IContentTypeRepository : IRepositoryQueryable, IReadRepository
{
///
/// Gets all entities of the specified query
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs
new file mode 100644
index 0000000000..005c1d62ba
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Persistence.Repositories
+{
+ public interface IDeleteMediaFilesRepository
+ {
+ ///
+ /// Called to remove all files associated with entities when an entity is permanently deleted
+ ///
+ ///
+ ///
+ bool DeleteMediaFiles(IEnumerable files);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
index 1149949e31..46bce6a03c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
@@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
- public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository
+ public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository
{
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs
index 54220e0b59..d28c59ac5b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs
@@ -1,10 +1,11 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
- public interface IMediaTypeRepository : IRepositoryQueryable
+ public interface IMediaTypeRepository : IRepositoryQueryable, IReadRepository
{
///
/// Gets all entities of the specified query
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
index 2436ffbfb6..9cb74d1806 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
@@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
- public interface IMemberRepository : IRepositoryVersionable
+ public interface IMemberRepository : IRepositoryVersionable, IDeleteMediaFilesRepository
{
///
/// Finds members in a given role
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs
index 9e91b1c87c..ae7739b28b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs
@@ -1,8 +1,9 @@
-using Umbraco.Core.Models;
+using System;
+using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
{
- public interface IMemberTypeRepository : IRepositoryQueryable
+ public interface IMemberTypeRepository : IRepositoryQueryable, IReadRepository
{
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs
index e1a464af66..a6ed95711e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs
@@ -18,12 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
bool EmptyRecycleBin();
-
- ///
- /// Called to remove all files associated with entities when recycle bin is emptied
- ///
- ///
- ///
- bool DeleteFiles(IEnumerable files);
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs
index 9cb4d938c7..ecee9af138 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs
@@ -15,23 +15,8 @@ namespace Umbraco.Core.Persistence.Repositories
}
- ///
- /// Defines the implementation of a Repository
- ///
- public interface IRepository : IRepository
- {
- ///
- /// Adds or Updates an Entity
- ///
- ///
- void AddOrUpdate(TEntity entity);
-
- ///
- /// Deletes an Entity
- ///
- ///
- void Delete(TEntity entity);
-
+ public interface IReadRepository : IRepository
+ {
///
/// Gets an Entity by Id
///
@@ -52,5 +37,25 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
bool Exists(TId id);
- }
+ }
+
+ //TODO: This should be decoupled! Shouldn't inherit from the IReadRepository and should be named IWriteRepository
+
+ ///
+ /// Defines the implementation of a Repository
+ ///
+ public interface IRepository : IReadRepository
+ {
+ ///
+ /// Adds or Updates an Entity
+ ///
+ ///
+ void AddOrUpdate(TEntity entity);
+
+ ///
+ /// Deletes an Entity
+ ///
+ ///
+ void Delete(TEntity entity);
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs
index c32f1b33a6..c0682f94f3 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs
@@ -4,6 +4,8 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
+ //TODO: This should be decoupled! Shouldn't inherit from the IRepository
+
///
/// Defines the implementation of a Repository, which allows queries against the
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
index 008e588311..4a9c110359 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Dynamics;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -31,8 +32,8 @@ namespace Umbraco.Core.Persistence.Repositories
private readonly ContentXmlRepository _contentXmlRepository;
private readonly ContentPreviewRepository _contentPreviewRepository;
- public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository)
- : base(work, cache, logger, sqlSyntax)
+ public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection)
+ : base(work, cache, logger, sqlSyntax, contentSection)
{
if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository");
if (tagRepository == null) throw new ArgumentNullException("tagRepository");
@@ -40,10 +41,10 @@ namespace Umbraco.Core.Persistence.Repositories
_tagRepository = tagRepository;
_contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
_contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
- EnsureUniqueNaming = true;
+ EnsureUniqueNaming = contentSection.EnsureUniqueNaming;
}
- public bool EnsureUniqueNaming { get; set; }
+ public bool EnsureUniqueNaming { get; private set; }
#region Overrides of RepositoryBase
@@ -235,7 +236,7 @@ namespace Umbraco.Core.Persistence.Repositories
var xmlItems = (from descendant in descendants
let xml = serializer(descendant)
- select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToString(SaveOptions.None) }).ToArray();
+ select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
//bulk insert it into the database
Database.BulkInsertRecords(xmlItems, tr);
@@ -283,10 +284,11 @@ namespace Umbraco.Core.Persistence.Repositories
//NOTE Should the logic below have some kind of fallback for empty parent ids ?
//Logic for setting Path, Level and SortOrder
var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId });
- int level = parent.Level + 1;
- int sortOrder =
- Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType",
- new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId });
+ var level = parent.Level + 1;
+ var maxSortOrder = Database.ExecuteScalar(
+ "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType",
+ new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId });
+ var sortOrder = maxSortOrder + 1;
//Create the (base) node data - umbracoNode
var nodeDto = dto.ContentDto.NodeDto;
diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs
index 13cdab400e..3bf70f1135 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs
@@ -28,31 +28,10 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IMediaType PerformGet(int id)
{
- var contentTypeSql = GetBaseQuery(false);
- contentTypeSql.Where(GetBaseWhereClause(), new { Id = id});
+ var contentTypes = ContentTypeQueryMapper.GetMediaTypes(
+ new[] { id }, Database, SqlSyntax, this);
- var dto = Database.Fetch(contentTypeSql).FirstOrDefault();
-
- if (dto == null)
- return null;
-
- var factory = new MediaTypeFactory(NodeObjectTypeId);
- var contentType = factory.BuildEntity(dto);
-
- contentType.AllowedContentTypes = GetAllowedContentTypeIds(id);
- contentType.PropertyGroups = GetPropertyGroupCollection(id, contentType.CreateDate, contentType.UpdateDate);
- ((MediaType)contentType).PropertyTypes = GetPropertyTypeCollection(id, contentType.CreateDate, contentType.UpdateDate);
-
- var list = Database.Fetch("WHERE childContentTypeId = @Id", new{ Id = id});
- foreach (var contentTypeDto in list)
- {
- bool result = contentType.AddContentType(Get(contentTypeDto.ParentId));
- //Do something if adding fails? (Should hopefully not be possible unless someone create a circular reference)
- }
-
- //on initial construction we don't want to have dirty properties tracked
- // http://issues.umbraco.org/issue/U4-1946
- ((Entity)contentType).ResetDirtyProperties(false);
+ var contentType = contentTypes.SingleOrDefault();
return contentType;
}
@@ -85,13 +64,18 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
+
+ ///
+ /// Gets all entities of the specified query
+ ///
+ ///
+ /// An enumerable list of objects
public IEnumerable GetByQuery(IQuery query)
{
- var ints = PerformGetByQuery(query);
- foreach (var i in ints)
- {
- yield return Get(i);
- }
+ var ints = PerformGetByQuery(query).ToArray();
+ return ints.Any()
+ ? GetAll(ints)
+ : Enumerable.Empty();
}
#region Overrides of PetaPocoRepositoryBase
@@ -189,5 +173,28 @@ namespace Umbraco.Core.Persistence.Repositories
{
get { throw new NotImplementedException(); }
}
+
+ protected override IMediaType PerformGet(Guid id)
+ {
+ var contentTypes = ContentTypeQueryMapper.GetMediaTypes(
+ new[] { id }, Database, SqlSyntax, this);
+
+ var contentType = contentTypes.SingleOrDefault();
+ return contentType;
+ }
+
+ protected override IEnumerable PerformGetAll(params Guid[] ids)
+ {
+ if (ids.Any())
+ {
+ return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this);
+ }
+ else
+ {
+ var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId);
+ var allIds = Database.Fetch(sql).ToArray();
+ return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
index 8c6edbd27e..02f5fb10c1 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
@@ -6,6 +6,7 @@ using System.Linq.Expressions;
using System.Text;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
@@ -33,8 +34,8 @@ namespace Umbraco.Core.Persistence.Repositories
private readonly ContentXmlRepository _contentXmlRepository;
private readonly ContentPreviewRepository _contentPreviewRepository;
- public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository)
- : base(work, cache, logger, sqlSyntax)
+ public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection)
+ : base(work, cache, logger, sqlSyntax, contentSection)
{
if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository");
if (tagRepository == null) throw new ArgumentNullException("tagRepository");
@@ -373,36 +374,6 @@ namespace Umbraco.Core.Persistence.Repositories
dirtyEntity.ResetDirtyProperties();
}
- protected override void PersistDeletedItem(IMember entity)
- {
- var fs = FileSystemProviderManager.Current.GetFileSystemProvider();
- var uploadFieldAlias = Constants.PropertyEditors.UploadFieldAlias;
- //Loop through properties to check if the media item contains images/file that should be deleted
- foreach (var property in ((Member)entity).Properties)
- {
- if (property.PropertyType.PropertyEditorAlias == uploadFieldAlias &&
- string.IsNullOrEmpty(property.Value.ToString()) == false
- && fs.FileExists(fs.GetRelativePath(property.Value.ToString())))
- {
- var relativeFilePath = fs.GetRelativePath(property.Value.ToString());
- var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath);
-
- // don't want to delete the media folder if not using directories.
- if (UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/"))
- {
- //issue U4-771: if there is a parent directory the recursive parameter should be true
- fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false);
- }
- else
- {
- fs.DeleteFile(relativeFilePath, true);
- }
- }
- }
-
- base.PersistDeletedItem(entity);
- }
-
#endregion
#region Overrides of VersionableRepositoryBase
@@ -480,7 +451,7 @@ namespace Umbraco.Core.Persistence.Repositories
var xmlItems = (from descendant in descendants
let xml = serializer(descendant)
- select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToString(SaveOptions.None) }).ToArray();
+ select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
//bulk insert it into the database
Database.BulkInsertRecords(xmlItems, tr);
@@ -791,5 +762,6 @@ namespace Umbraco.Core.Persistence.Repositories
_contentXmlRepository.Dispose();
_contentPreviewRepository.Dispose();
}
+
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs
index d9c8e815be..4d2fbe30de 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs
@@ -268,6 +268,42 @@ namespace Umbraco.Core.Persistence.Repositories
propertyTypeAlias);
}
+ protected override IMemberType PerformGet(Guid id)
+ {
+ var sql = GetBaseQuery(false);
+ sql.Where("umbracoNode.uniqueID = @Id", new { Id = id });
+ sql.OrderByDescending(x => x.NodeId);
+
+ var dtos =
+ Database.Fetch(
+ new PropertyTypePropertyGroupRelator().Map, sql);
+
+ if (dtos == null || dtos.Any() == false)
+ return null;
+
+ var factory = new MemberTypeReadOnlyFactory();
+ var member = factory.BuildEntity(dtos.First());
+
+ return member;
+ }
+
+ protected override IEnumerable PerformGetAll(params Guid[] ids)
+ {
+ var sql = GetBaseQuery(false);
+ if (ids.Any())
+ {
+ var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.uniqueID='{0}'", x)));
+ sql.Where(statement);
+ }
+ sql.OrderByDescending(x => x.NodeId, SqlSyntax);
+
+ var dtos =
+ Database.Fetch(
+ new PropertyTypePropertyGroupRelator().Map, sql);
+
+ return BuildFromDtos(dtos);
+ }
+
///
/// Ensure that all the built-in membership provider properties have their correct data type
/// and property editors assigned. This occurs prior to saving so that the correct values are persisted.
diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs
index 0599afce82..977c2a54cd 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories
internal abstract class RecycleBinRepository : VersionableRepositoryBase, IRecycleBinRepository
where TEntity : class, IUmbracoEntity
{
- protected RecycleBinRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
- : base(work, cache, logger, sqlSyntax)
+ protected RecycleBinRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection)
+ : base(work, cache, logger, sqlSyntax, contentSection)
{
}
@@ -81,51 +82,6 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
- ///
- /// Deletes all files passed in.
- ///
- ///
- ///
- public virtual bool DeleteFiles(IEnumerable files)
- {
- //ensure duplicates are removed
- files = files.Distinct();
-
- var allsuccess = true;
-
- var fs = FileSystemProviderManager.Current.GetFileSystemProvider();
- Parallel.ForEach(files, file =>
- {
- try
- {
- if (file.IsNullOrWhiteSpace()) return;
-
- var relativeFilePath = fs.GetRelativePath(file);
- if (fs.FileExists(relativeFilePath) == false) return;
-
- var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath);
-
- // don't want to delete the media folder if not using directories.
- if (UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/"))
- {
- //issue U4-771: if there is a parent directory the recursive parameter should be true
- fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false);
- }
- else
- {
- fs.DeleteFile(file, true);
- }
- }
- catch (Exception e)
- {
- Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e);
- allsuccess = false;
- }
- });
-
- return allsuccess;
- }
-
private string FormatDeleteStatement(string tableName, string keyName)
{
//This query works with sql ce and sql server:
diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs
index 86eb2964fc..2bbac2a280 100644
--- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs
@@ -120,7 +120,6 @@ namespace Umbraco.Core.Persistence.Repositories
"DELETE FROM cmsTask WHERE parentUserId = @Id",
"DELETE FROM umbracoUser2NodePermission WHERE userId = @Id",
"DELETE FROM umbracoUser2NodeNotify WHERE userId = @Id",
- "DELETE FROM umbracoUserLogins WHERE userID = @Id",
"DELETE FROM umbracoUser2app WHERE " + SqlSyntax.GetQuotedColumnName("user") + "=@Id",
"DELETE FROM umbracoUser WHERE id = @Id",
"DELETE FROM umbracoExternalLogin WHERE id = @Id"
diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
index 27fe5ef4ad..207cb1d04e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
@@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
@@ -17,16 +20,19 @@ using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Dynamics;
+using Umbraco.Core.IO;
namespace Umbraco.Core.Persistence.Repositories
{
internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase
where TEntity : class, IAggregateRoot
{
+ private readonly IContentSection _contentSection;
- protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
+ protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection)
: base(work, cache, logger, sqlSyntax)
{
+ _contentSection = contentSection;
}
#region IRepositoryVersionable Implementation
@@ -395,7 +401,7 @@ ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arg
// below if any property requires tag support
var allPreValues = new Lazy>(() =>
{
- var preValsSql = new Sql(@"SELECT a.id as preValId, a.value, a.sortorder, a.alias, a.datatypeNodeId
+ var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
FROM cmsDataTypePreValues a
WHERE EXISTS(
SELECT DISTINCT b.id as preValIdInner
@@ -535,5 +541,50 @@ WHERE EXISTS(
return orderBy;
}
}
+
+ ///
+ /// Deletes all media files passed in.
+ ///
+ ///
+ ///
+ public virtual bool DeleteMediaFiles(IEnumerable files)
+ {
+ //ensure duplicates are removed
+ files = files.Distinct();
+
+ var allsuccess = true;
+
+ var fs = FileSystemProviderManager.Current.GetFileSystemProvider();
+ Parallel.ForEach(files, file =>
+ {
+ try
+ {
+ if (file.IsNullOrWhiteSpace()) return;
+
+ var relativeFilePath = fs.GetRelativePath(file);
+ if (fs.FileExists(relativeFilePath) == false) return;
+
+ var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath);
+
+ // don't want to delete the media folder if not using directories.
+ if (_contentSection.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/"))
+ {
+ //issue U4-771: if there is a parent directory the recursive parameter should be true
+ fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false);
+ }
+ else
+ {
+ fs.DeleteFile(file, true);
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e);
+ allsuccess = false;
+ }
+ });
+
+ return allsuccess;
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs
index 787a06f005..19556b1f31 100644
--- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs
+++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs
@@ -108,7 +108,8 @@ namespace Umbraco.Core.Persistence
_sqlSyntax,
CreateContentTypeRepository(uow),
CreateTemplateRepository(uow),
- CreateTagRepository(uow))
+ CreateTagRepository(uow),
+ _settings.Content)
{
EnsureUniqueNaming = _settings.Content.EnsureUniqueNaming
};
@@ -158,7 +159,8 @@ namespace Umbraco.Core.Persistence
_cacheHelper,
_logger, _sqlSyntax,
CreateMediaTypeRepository(uow),
- CreateTagRepository(uow)) { EnsureUniqueNaming = _settings.Content.EnsureUniqueNaming };
+ CreateTagRepository(uow),
+ _settings.Content);
}
public virtual IMediaTypeRepository CreateMediaTypeRepository(IDatabaseUnitOfWork uow)
@@ -266,7 +268,8 @@ namespace Umbraco.Core.Persistence
_logger, _sqlSyntax,
CreateMemberTypeRepository(uow),
CreateMemberGroupRepository(uow),
- CreateTagRepository(uow));
+ CreateTagRepository(uow),
+ _settings.Content);
}
public virtual IMemberTypeRepository CreateMemberTypeRepository(IDatabaseUnitOfWork uow)
diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs
index 89e17438f3..d773eed2e9 100644
--- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
+using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -84,7 +85,7 @@ namespace Umbraco.Core.PropertyEditors
Name = att.Name,
Description = att.Description,
HideLabel = att.HideLabel,
- View = att.View
+ View = att.View.StartsWith("~/") ? IOHelper.ResolveUrl(att.View) : att.View
};
}
diff --git a/src/Umbraco.Core/PropertyEditors/TagPropertyDefinition.cs b/src/Umbraco.Core/PropertyEditors/TagPropertyDefinition.cs
index 978a7bf2ba..fd314ba9b7 100644
--- a/src/Umbraco.Core/PropertyEditors/TagPropertyDefinition.cs
+++ b/src/Umbraco.Core/PropertyEditors/TagPropertyDefinition.cs
@@ -31,7 +31,10 @@ namespace Umbraco.Core.PropertyEditors
Delimiter = tagsAttribute.Delimiter;
ReplaceTags = tagsAttribute.ReplaceTags;
TagGroup = tagsAttribute.TagGroup;
- StorageType = TagCacheStorageType.Csv;
+
+ var preValues = propertySaving.PreValues.PreValuesAsDictionary;
+ StorageType = preValues.ContainsKey("storageType") && preValues["storageType"].Value == TagCacheStorageType.Json.ToString() ?
+ TagCacheStorageType.Json : TagCacheStorageType.Csv;
}
///
diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs
new file mode 100644
index 0000000000..fc7ecf5330
--- /dev/null
+++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Identity;
+using Microsoft.AspNet.Identity.Owin;
+using Microsoft.Owin;
+using Microsoft.Owin.Security;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Models.Identity;
+
+namespace Umbraco.Core.Security
+{
+ public class BackOfficeSignInManager : SignInManager
+ {
+ public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager)
+ : base(userManager, authenticationManager)
+ {
+ AuthenticationType = Constants.Security.BackOfficeAuthenticationType;
+ }
+
+ public override Task CreateUserIdentityAsync(BackOfficeIdentityUser user)
+ {
+ return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager);
+ }
+
+ public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context)
+ {
+ return new BackOfficeSignInManager(context.GetUserManager(), context.Authentication);
+ }
+
+ ///
+ /// Creates a user identity and then signs the identity using the AuthenticationManager
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent, bool rememberBrowser)
+ {
+ var userIdentity = await CreateUserIdentityAsync(user);
+
+ // Clear any partial cookies from external or two factor partial sign ins
+ AuthenticationManager.SignOut(
+ Constants.Security.BackOfficeExternalAuthenticationType,
+ Constants.Security.BackOfficeTwoFactorAuthenticationType);
+
+ var nowUtc = DateTime.Now.ToUniversalTime();
+
+ if (rememberBrowser)
+ {
+ var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
+ AuthenticationManager.SignIn(new AuthenticationProperties()
+ {
+ IsPersistent = isPersistent,
+ AllowRefresh = true,
+ IssuedUtc = nowUtc,
+ ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes)
+ }, userIdentity, rememberBrowserIdentity);
+ }
+ else
+ {
+ AuthenticationManager.SignIn(new AuthenticationProperties()
+ {
+ IsPersistent = isPersistent,
+ AllowRefresh = true,
+ IssuedUtc = nowUtc,
+ ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes)
+ }, userIdentity);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
index def46b7556..f86c06c39c 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
@@ -1,17 +1,16 @@
using System;
using System.Linq;
-using System.Security.Claims;
using System.Text;
-using System.Threading.Tasks;
using System.Web.Security;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
-using Microsoft.Owin;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Services;
namespace Umbraco.Core.Security
{
+
+
///
/// Default back office user manager
///
@@ -89,6 +88,7 @@ namespace Umbraco.Core.Security
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false
+ //TODO: Do we support the old regex match thing that membership providers used?
};
//use a custom hasher based on our membership provider
@@ -100,6 +100,13 @@ namespace Umbraco.Core.Security
manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity"));
}
+ manager.UserLockoutEnabledByDefault = true;
+ manager.MaxFailedAccessAttemptsBeforeLockout = membershipProvider.MaxInvalidPasswordAttempts;
+ //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked
+ // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are
+ // locked out or not.
+ manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(30);
+
//custom identity factory for creating the identity object for which we auth against in the back office
manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory();
@@ -149,13 +156,9 @@ namespace Umbraco.Core.Security
get { return false; }
}
- //TODO: Support this
- public override bool SupportsUserLockout
- {
- get { return false; }
- }
-
- //TODO: Support this
+ ///
+ /// Developers will need to override this to support custom 2 factor auth
+ ///
public override bool SupportsUserTwoFactor
{
get { return false; }
diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
index f6d8222c44..e37f9d1a54 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using System.Web.Security;
using AutoMapper;
using Microsoft.AspNet.Identity;
+using Microsoft.Owin;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
@@ -17,15 +18,12 @@ namespace Umbraco.Core.Security
IUserEmailStore,
IUserLoginStore,
IUserRoleStore,
- IUserSecurityStampStore
-
+ IUserSecurityStampStore,
+ IUserLockoutStore,
+ IUserTwoFactorStore
+
//TODO: This would require additional columns/tables for now people will need to implement this on their own
//IUserPhoneNumberStore,
- //IUserTwoFactorStore,
-
- //TODO: This will require additional columns/tables
- //IUserLockoutStore
-
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
//IQueryableUserStore
{
@@ -75,12 +73,12 @@ namespace Umbraco.Core.Security
{
DefaultToLiveEditing = false,
Email = user.Email,
- Language = Configuration.GlobalSettings.DefaultUILanguage,
+ Language = user.Culture ?? Configuration.GlobalSettings.DefaultUILanguage,
Name = user.Name,
Username = user.UserName,
- StartContentId = -1,
- StartMediaId = -1,
- IsLockedOut = false,
+ StartContentId = user.StartContentId == 0 ? -1 : user.StartContentId,
+ StartMediaId = user.StartMediaId == 0 ? -1 : user.StartMediaId,
+ IsLockedOut = user.IsLockedOut,
IsApproved = true
};
@@ -168,7 +166,7 @@ namespace Umbraco.Core.Security
///
///
///
- public Task FindByIdAsync(int userId)
+ public async Task FindByIdAsync(int userId)
{
ThrowIfDisposed();
var user = _userService.GetUserById(userId);
@@ -176,7 +174,7 @@ namespace Umbraco.Core.Security
{
return null;
}
- return Task.FromResult(AssignLoginsCallback(Mapper.Map(user)));
+ return await Task.FromResult(AssignLoginsCallback(Mapper.Map(user)));
}
///
@@ -184,7 +182,7 @@ namespace Umbraco.Core.Security
///
///
///
- public Task FindByNameAsync(string userName)
+ public async Task FindByNameAsync(string userName)
{
ThrowIfDisposed();
var user = _userService.GetByUsername(userName);
@@ -195,7 +193,7 @@ namespace Umbraco.Core.Security
var result = AssignLoginsCallback(Mapper.Map(user));
- return Task.FromResult(result);
+ return await Task.FromResult(result);
}
///
@@ -506,6 +504,118 @@ namespace Umbraco.Core.Security
return user;
}
+ ///
+ /// Sets whether two factor authentication is enabled for the user
+ ///
+ ///
+ ///
+ public virtual Task SetTwoFactorEnabledAsync(BackOfficeIdentityUser user, bool enabled)
+ {
+ user.TwoFactorEnabled = false;
+ return Task.FromResult(0);
+ }
+
+ ///
+ /// Returns whether two factor authentication is enabled for the user
+ ///
+ ///
+ ///
+ public virtual Task GetTwoFactorEnabledAsync(BackOfficeIdentityUser user)
+ {
+ return Task.FromResult(false);
+ }
+
+ #region IUserLockoutStore
+
+ ///
+ /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out.
+ ///
+ ///
+ ///
+ ///
+ /// Currently we do not suport a timed lock out, when they are locked out, an admin will have to reset the status
+ ///
+ public Task GetLockoutEndDateAsync(BackOfficeIdentityUser user)
+ {
+ if (user == null) throw new ArgumentNullException("user");
+
+ return user.LockoutEndDateUtc.HasValue
+ ? Task.FromResult(DateTimeOffset.MaxValue)
+ : Task.FromResult(DateTimeOffset.MinValue);
+ }
+
+ ///
+ /// Locks a user out until the specified end date (set to a past date, to unlock a user)
+ ///
+ ///
+ ///
+ public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset lockoutEnd)
+ {
+ if (user == null) throw new ArgumentNullException("user");
+ user.LockoutEndDateUtc = lockoutEnd.UtcDateTime;
+ return Task.FromResult(0);
+ }
+
+ ///
+ /// Used to record when an attempt to access the user has failed
+ ///
+ ///
+ ///
+ public Task IncrementAccessFailedCountAsync(BackOfficeIdentityUser user)
+ {
+ if (user == null) throw new ArgumentNullException("user");
+ user.AccessFailedCount++;
+ return Task.FromResult(user.AccessFailedCount);
+ }
+
+ ///
+ /// Used to reset the access failed count, typically after the account is successfully accessed
+ ///
+ ///
+ ///
+ public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user)
+ {
+ if (user == null) throw new ArgumentNullException("user");
+ user.AccessFailedCount = 0;
+ return Task.FromResult(0);
+ }
+
+ ///
+ /// Returns the current number of failed access attempts. This number usually will be reset whenever the password is
+ /// verified or the account is locked out.
+ ///
+ ///
+ ///
+ public Task GetAccessFailedCountAsync(BackOfficeIdentityUser user)
+ {
+ if (user == null) throw new ArgumentNullException("user");
+ return Task.FromResult(user.AccessFailedCount);
+ }
+
+ ///
+ /// Returns true
+ ///
+ ///
+ ///
+ public Task GetLockoutEnabledAsync(BackOfficeIdentityUser user)
+ {
+ if (user == null) throw new ArgumentNullException("user");
+ return Task.FromResult(user.LockoutEnabled);
+ }
+
+ ///
+ /// Doesn't actually perform any function, users can always be locked out
+ ///
+ ///
+ ///
+ public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled)
+ {
+ if (user == null) throw new ArgumentNullException("user");
+ user.LockoutEnabled = enabled;
+ return Task.FromResult(0);
+ }
+ #endregion
+
private bool UpdateMemberProperties(Models.Membership.IUser user, BackOfficeIdentityUser identityUser)
{
var anythingChanged = false;
@@ -526,10 +636,10 @@ namespace Umbraco.Core.Security
anythingChanged = true;
user.FailedPasswordAttempts = identityUser.AccessFailedCount;
}
- if (user.IsLockedOut != identityUser.LockoutEnabled)
+ if (user.IsLockedOut != identityUser.IsLockedOut)
{
anythingChanged = true;
- user.IsLockedOut = identityUser.LockoutEnabled;
+ user.IsLockedOut = identityUser.IsLockedOut;
}
if (user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false)
{
@@ -562,6 +672,7 @@ namespace Umbraco.Core.Security
anythingChanged = true;
user.SecurityStamp = identityUser.SecurityStamp;
}
+
if (user.AllowedSections.ContainsAll(identityUser.AllowedSections) == false
|| identityUser.AllowedSections.ContainsAll(user.AllowedSections) == false)
{
@@ -579,10 +690,13 @@ namespace Umbraco.Core.Security
return anythingChanged;
}
+
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
}
+
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs
index 794c8fda2d..392c1545d1 100644
--- a/src/Umbraco.Core/Security/MembershipProviderBase.cs
+++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs
@@ -225,7 +225,7 @@ namespace Umbraco.Core.Security
_enablePasswordReset = config.GetValue("enablePasswordReset", false);
_requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false);
_requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true);
- _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 20, false, 0);
+ _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
_passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
_minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80);
_minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80);
diff --git a/src/Umbraco.Core/SemVersionExtensions.cs b/src/Umbraco.Core/SemVersionExtensions.cs
new file mode 100644
index 0000000000..f45aacdfc2
--- /dev/null
+++ b/src/Umbraco.Core/SemVersionExtensions.cs
@@ -0,0 +1,12 @@
+using Semver;
+
+namespace Umbraco.Core
+{
+ public static class SemVersionExtensions
+ {
+ public static string ToSemanticString(this SemVersion semVersion)
+ {
+ return semVersion.ToString().Replace("--", "-").Replace("-+", "+");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index 57da3bd8b3..b7ad8c4d03 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1111,7 +1111,7 @@ namespace Umbraco.Core.Services
Deleted.RaiseEvent(args, this);
//remove any flagged media files
- repository.DeleteFiles(args.MediaFilesToDelete);
+ repository.DeleteMediaFiles(args.MediaFilesToDelete);
}
Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id);
@@ -1310,7 +1310,7 @@ namespace Umbraco.Core.Services
EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, entities, files, success), this);
if (success)
- repository.DeleteFiles(files);
+ repository.DeleteMediaFiles(files);
}
@@ -1632,7 +1632,7 @@ namespace Umbraco.Core.Services
{
var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, content);
- var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToString(SaveOptions.None) };
+ var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToDataString() };
var exists =
uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) !=
null;
diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs
index 9bff23a99b..194871f252 100644
--- a/src/Umbraco.Core/Services/ContentTypeService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeService.cs
@@ -185,6 +185,19 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets an object by its Key
+ ///
+ /// Alias of the to retrieve
+ ///
+ public IContentType GetContentType(Guid id)
+ {
+ using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ return repository.Get(id);
+ }
+ }
+
///
/// Gets a list of all available objects
///
@@ -198,6 +211,19 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets a list of all available objects
+ ///
+ /// Optional list of ids
+ /// An Enumerable list of objects
+ public IEnumerable GetAllContentTypes(IEnumerable ids)
+ {
+ using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ return repository.GetAll(ids.ToArray());
+ }
+ }
+
///
/// Gets a list of children for a object
///
@@ -213,25 +239,22 @@ namespace Umbraco.Core.Services
}
}
- /////
- ///// Returns the content type descendant Ids for the content type specified
- /////
- /////
- /////
- //internal IEnumerable GetDescendantContentTypeIds(int contentTypeId)
- //{
- // using (var uow = UowProvider.GetUnitOfWork())
- // {
- // //method to return the child content type ids for the id specified
- // Func getChildIds =
- // parentId =>
- // uow.Database.Fetch("WHERE parentContentTypeId = @Id", new {Id = parentId})
- // .Select(x => x.ChildId).ToArray();
-
- // //recursively get all descendant ids
- // return getChildIds(contentTypeId).FlattenList(getChildIds);
- // }
- //}
+ ///
+ /// Gets a list of children for a object
+ ///
+ /// Id of the Parent
+ /// An Enumerable list of objects
+ public IEnumerable GetContentTypeChildren(Guid id)
+ {
+ using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ var found = GetContentType(id);
+ if (found == null) return Enumerable.Empty();
+ var query = Query.Builder.Where(x => x.ParentId == found.Id);
+ var contentTypes = repository.GetByQuery(query);
+ return contentTypes;
+ }
+ }
///
/// Checks whether an item has any children
@@ -248,6 +271,23 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Checks whether an item has any children
+ ///
+ /// Id of the
+ /// True if the content type has any children otherwise False
+ public bool HasChildren(Guid id)
+ {
+ using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ var found = GetContentType(id);
+ if (found == null) return false;
+ var query = Query.Builder.Where(x => x.ParentId == found.Id);
+ int count = repository.Count(query);
+ return count > 0;
+ }
+ }
+
///
/// This is called after an IContentType is saved and is used to update the content xml structures in the database
/// if they are required to be updated.
@@ -518,6 +558,19 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets an object by its Id
+ ///
+ /// Id of the to retrieve
+ ///
+ public IMediaType GetMediaType(Guid id)
+ {
+ using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ return repository.Get(id);
+ }
+ }
+
///
/// Gets a list of all available objects
///
@@ -531,6 +584,19 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets a list of all available objects
+ ///
+ /// Optional list of ids
+ /// An Enumerable list of objects
+ public IEnumerable GetAllMediaTypes(IEnumerable ids)
+ {
+ using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ return repository.GetAll(ids.ToArray());
+ }
+ }
+
///
/// Gets a list of children for a object
///
@@ -546,6 +612,23 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets a list of children for a object
+ ///
+ /// Id of the Parent
+ /// An Enumerable list of objects
+ public IEnumerable GetMediaTypeChildren(Guid id)
+ {
+ using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ var found = GetMediaType(id);
+ if (found == null) return Enumerable.Empty();
+ var query = Query.Builder.Where(x => x.ParentId == found.Id);
+ var contentTypes = repository.GetByQuery(query);
+ return contentTypes;
+ }
+ }
+
///
/// Checks whether an item has any children
///
@@ -561,6 +644,23 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Checks whether an item has any children
+ ///
+ /// Id of the
+ /// True if the media type has any children otherwise False
+ public bool MediaTypeHasChildren(Guid id)
+ {
+ using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
+ {
+ var found = GetMediaType(id);
+ if (found == null) return false;
+ var query = Query.Builder.Where(x => x.ParentId == found.Id);
+ int count = repository.Count(query);
+ return count > 0;
+ }
+ }
+
///
/// Saves a single object
///
diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs
index a538840e36..1eb0b8695c 100644
--- a/src/Umbraco.Core/Services/IContentTypeService.cs
+++ b/src/Umbraco.Core/Services/IContentTypeService.cs
@@ -68,6 +68,13 @@ namespace Umbraco.Core.Services
///
IContentType GetContentType(string alias);
+ ///
+ /// Gets an object by its Key
+ ///
+ /// Alias of the to retrieve
+ ///
+ IContentType GetContentType(Guid id);
+
///
/// Gets a list of all available objects
///
@@ -75,6 +82,13 @@ namespace Umbraco.Core.Services
/// An Enumerable list of objects
IEnumerable GetAllContentTypes(params int[] ids);
+ ///
+ /// Gets a list of all available objects
+ ///
+ /// Optional list of ids
+ /// An Enumerable list of objects
+ IEnumerable GetAllContentTypes(IEnumerable ids);
+
///
/// Gets a list of children for a object
///
@@ -82,6 +96,13 @@ namespace Umbraco.Core.Services
/// An Enumerable list of objects
IEnumerable GetContentTypeChildren(int id);
+ ///
+ /// Gets a list of children for a object
+ ///
+ /// Id of the Parent
+ /// An Enumerable list of objects
+ IEnumerable GetContentTypeChildren(Guid id);
+
///
/// Saves a single object
///
@@ -126,6 +147,13 @@ namespace Umbraco.Core.Services
///
IMediaType GetMediaType(string alias);
+ ///
+ /// Gets an object by its Id
+ ///
+ /// Id of the to retrieve
+ ///
+ IMediaType GetMediaType(Guid id);
+
///
/// Gets a list of all available objects
///
@@ -133,6 +161,13 @@ namespace Umbraco.Core.Services
/// An Enumerable list of objects
IEnumerable GetAllMediaTypes(params int[] ids);
+ ///
+ /// Gets a list of all available objects
+ ///
+ /// Optional list of ids
+ /// An Enumerable list of objects
+ IEnumerable GetAllMediaTypes(IEnumerable ids);
+
///
/// Gets a list of children for a object
///
@@ -140,6 +175,13 @@ namespace Umbraco.Core.Services
/// An Enumerable list of objects
IEnumerable GetMediaTypeChildren(int id);
+ ///
+ /// Gets a list of children for a object
+ ///
+ /// Id of the Parent
+ /// An Enumerable list of objects
+ IEnumerable GetMediaTypeChildren(Guid id);
+
///
/// Saves a single object
///
@@ -189,11 +231,25 @@ namespace Umbraco.Core.Services
/// True if the content type has any children otherwise False
bool HasChildren(int id);
+ ///
+ /// Checks whether an item has any children
+ ///
+ /// Id of the
+ /// True if the content type has any children otherwise False
+ bool HasChildren(Guid id);
+
///
/// Checks whether an item has any children
///
/// Id of the
/// True if the media type has any children otherwise False
bool MediaTypeHasChildren(int id);
+
+ ///
+ /// Checks whether an item has any children
+ ///
+ /// Id of the
+ /// True if the media type has any children otherwise False
+ bool MediaTypeHasChildren(Guid id);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs
index bd695b65e5..59b77085af 100644
--- a/src/Umbraco.Core/Services/LocalizationService.cs
+++ b/src/Umbraco.Core/Services/LocalizationService.cs
@@ -18,7 +18,6 @@ namespace Umbraco.Core.Services
///
public class LocalizationService : RepositoryService, ILocalizationService
{
- private static readonly Guid RootParentId = new Guid(Constants.Conventions.Localization.DictionaryItemRootId);
[Obsolete("Use the constructors that specify all dependencies instead")]
public LocalizationService()
@@ -93,7 +92,7 @@ namespace Umbraco.Core.Services
}
}
- var item = new DictionaryItem(parentId.HasValue ? parentId.Value : RootParentId, key);
+ var item = new DictionaryItem(parentId, key);
if (defaultValue.IsNullOrWhiteSpace() == false)
{
@@ -188,7 +187,7 @@ namespace Umbraco.Core.Services
{
using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork()))
{
- var query = Query.Builder.Where(x => x.ParentId == RootParentId);
+ var query = Query.Builder.Where(x => x.ParentId == null);
var items = repository.GetByQuery(query);
return items;
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index 1c7355ef42..299d089319 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -825,7 +825,7 @@ namespace Umbraco.Core.Services
EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, entities, files, success), this);
if (success)
- repository.DeleteFiles(files);
+ repository.DeleteMediaFiles(files);
}
}
Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21);
@@ -910,7 +910,7 @@ namespace Umbraco.Core.Services
Deleted.RaiseEvent(args, this);
//remove any flagged media files
- repository.DeleteFiles(args.MediaFilesToDelete);
+ repository.DeleteMediaFiles(args.MediaFilesToDelete);
}
Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id);
@@ -1160,7 +1160,7 @@ namespace Umbraco.Core.Services
//private void CreateAndSaveMediaXml(XElement xml, int id, UmbracoDatabase db)
//{
- // var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToString(SaveOptions.None) };
+ // var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToDataString() };
// var exists = db.FirstOrDefault("WHERE nodeId = @Id", new { Id = id }) != null;
// int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco));
//}
diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs
index 9b444de296..c6c49da7e9 100644
--- a/src/Umbraco.Core/Services/MemberService.cs
+++ b/src/Umbraco.Core/Services/MemberService.cs
@@ -80,7 +80,7 @@ namespace Umbraco.Core.Services
{
using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork()))
{
- var types = repository.GetAll().Select(x => x.Alias).ToArray();
+ var types = repository.GetAll(new int[]{}).Select(x => x.Alias).ToArray();
if (types.Any() == false)
{
@@ -132,11 +132,11 @@ namespace Umbraco.Core.Services
//go re-fetch the member and update the properties that may have changed
var result = GetByUsername(member.Username);
-
+
//should never be null but it could have been deleted by another thread.
- if (result == null)
+ if (result == null)
return;
-
+
member.RawPasswordValue = result.RawPasswordValue;
member.LastPasswordChangeDate = result.LastPasswordChangeDate;
member.UpdateDate = member.UpdateDate;
@@ -975,9 +975,13 @@ namespace Umbraco.Core.Services
{
repository.Delete(member);
uow.Commit();
- }
- Deleted.RaiseEvent(new DeleteEventArgs(member, false), this);
+ var args = new DeleteEventArgs(member, false);
+ Deleted.RaiseEvent(args, this);
+
+ //remove any flagged media files
+ repository.DeleteMediaFiles(args.MediaFilesToDelete);
+ }
}
///
@@ -1169,7 +1173,7 @@ namespace Umbraco.Core.Services
repository.DissociateRoles(usernames, roleNames);
}
}
-
+
public void AssignRole(int memberId, string roleName)
{
AssignRoles(new[] { memberId }, new[] { roleName });
@@ -1198,7 +1202,7 @@ namespace Umbraco.Core.Services
}
}
-
+
#endregion
@@ -1233,7 +1237,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
}
-
+
#region Event Handlers
///
@@ -1250,7 +1254,7 @@ namespace Umbraco.Core.Services
/// Occurs before Save
///
public static event TypedEventHandler> Saving;
-
+
///
/// Occurs after Create
///
diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs
index 961175f455..dd4866c8c3 100644
--- a/src/Umbraco.Core/Services/UserService.cs
+++ b/src/Umbraco.Core/Services/UserService.cs
@@ -243,10 +243,6 @@ namespace Umbraco.Core.Services
membershipUser.Username = DateTime.Now.ToString("yyyyMMdd") + "_" + membershipUser.Username;
}
Save(membershipUser);
-
- //clear out the user logins!
- var uow = UowProvider.GetUnitOfWork();
- uow.Database.Execute("delete from umbracoUserLogins where userID = @id", new {id = membershipUser.Id});
}
///
diff --git a/src/Umbraco.Core/Sync/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/BatchedDatabaseServerMessenger.cs
index 9eaf557170..b16caa8779 100644
--- a/src/Umbraco.Core/Sync/BatchedDatabaseServerMessenger.cs
+++ b/src/Umbraco.Core/Sync/BatchedDatabaseServerMessenger.cs
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Sync
{
UtcStamp = DateTime.UtcNow,
Instructions = JsonConvert.SerializeObject(instructions, Formatting.None),
- OriginIdentity = GetLocalIdentity()
+ OriginIdentity = LocalIdentity
};
ApplicationContext.DatabaseContext.Database.Insert(dto);
diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
index e6ce565280..e9be30ab09 100644
--- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
+++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -78,7 +79,7 @@ namespace Umbraco.Core.Sync
{
UtcStamp = DateTime.UtcNow,
Instructions = JsonConvert.SerializeObject(instructions, Formatting.None),
- OriginIdentity = GetLocalIdentity()
+ OriginIdentity = LocalIdentity
};
ApplicationContext.DatabaseContext.Database.Insert(dto);
@@ -186,7 +187,7 @@ namespace Umbraco.Core.Sync
// only process instructions coming from a remote server, and ignore instructions coming from
// the local server as they've already been processed. We should NOT assume that the sequence of
// instructions in the database makes any sense whatsoever, because it's all async.
- var localIdentity = GetLocalIdentity();
+ var localIdentity = LocalIdentity;
var lastId = 0;
foreach (var dto in dtos)
@@ -269,17 +270,20 @@ namespace Umbraco.Core.Sync
}
///
- /// Gets the local server unique identity.
+ /// Gets the unique local identity of the executing AppDomain.
///
- /// The unique identity of the local server.
- protected string GetLocalIdentity()
- {
- return JsonConvert.SerializeObject(new
- {
- machineName = NetworkHelper.MachineName,
- appDomainAppId = HttpRuntime.AppDomainAppId
- });
- }
+ ///
+ /// It is not only about the "server" (machine name and appDomainappId), but also about
+ /// an AppDomain, within a Process, on that server - because two AppDomains running at the same
+ /// time on the same server (eg during a restart) are, practically, a LB setup.
+ /// Practically, all we really need is the guid, the other infos are here for information
+ /// and debugging purposes.
+ ///
+ protected readonly static string LocalIdentity = NetworkHelper.MachineName // eg DOMAIN\SERVER
+ + "/" + HttpRuntime.AppDomainAppId // eg /LM/S3SVC/11/ROOT
+ + " [P" + Process.GetCurrentProcess().Id // eg 1234
+ + "/D" + AppDomain.CurrentDomain.Id // eg 22
+ + "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique
///
/// Gets the sync file path for the local server.
diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs
index 66b845f4ec..f1bebce10b 100644
--- a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs
+++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Sync
///
public DatabaseServerMessengerOptions()
{
- DaysToRetainInstructions = 100; // 100 days
+ DaysToRetainInstructions = 2; // 2 days
ThrottleSeconds = 5; // 5 seconds
}
diff --git a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
index 1d5ee7855a..e79a0f9209 100644
--- a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
+++ b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
@@ -1,10 +1,9 @@
-using System;
using System.Linq;
using System.Web;
-using System.Xml;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
namespace Umbraco.Core.Sync
{
@@ -13,58 +12,73 @@ namespace Umbraco.Core.Sync
///
internal static class ServerEnvironmentHelper
{
- ///
- /// Returns the current umbraco base url for the current server depending on it's environment
- /// status. This will attempt to determine the internal umbraco base url that can be used by the current
- /// server to send a request to itself if it is in a load balanced environment.
- ///
- /// The full base url including schema (i.e. http://myserver:80/umbraco ) - or null if the url
- /// cannot be determined at the moment (usually because the first request has not properly completed yet).
- public static string GetCurrentServerUmbracoBaseUrl(ApplicationContext appContext, IUmbracoSettingsSection settings)
+ public static void TrySetApplicationUrlFromSettings(ApplicationContext appContext, ILogger logger, IUmbracoSettingsSection settings)
{
+ // try umbracoSettings:settings/web.routing/@umbracoApplicationUrl
+ // which is assumed to:
+ // - end with SystemDirectories.Umbraco
+ // - contain a scheme
+ // - end or not with a slash, it will be taken care of
+ // eg "http://www.mysite.com/umbraco"
+ var url = settings.WebRouting.UmbracoApplicationUrl;
+ if (url.IsNullOrWhiteSpace() == false)
+ {
+ appContext.UmbracoApplicationUrl = url.TrimEnd('/');
+ logger.Info("ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using web.routing/@umbracoApplicationUrl)");
+ return;
+ }
+
+ // try umbracoSettings:settings/scheduledTasks/@baseUrl
+ // which is assumed to:
+ // - end with SystemDirectories.Umbraco
+ // - NOT contain any scheme (because, legacy)
+ // - end or not with a slash, it will be taken care of
+ // eg "mysite.com/umbraco"
+ url = settings.ScheduledTasks.BaseUrl;
+ if (url.IsNullOrWhiteSpace() == false)
+ {
+ var ssl = GlobalSettings.UseSSL ? "s" : "";
+ url = "http" + ssl + "://" + url;
+ appContext.UmbracoApplicationUrl = url.TrimEnd('/');
+ logger.Info("ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using scheduledTasks/@baseUrl)");
+ return;
+ }
+
+ // try servers
var status = GetStatus(settings);
-
if (status == CurrentServerEnvironmentStatus.Single)
- {
- // single install, return null if no config/original url, else use config/original url as base
- // use http or https as appropriate
- return GetBaseUrl(appContext, settings);
- }
+ return;
+ // no server, nothing we can do
var servers = settings.DistributedCall.Servers.ToArray();
+ if (servers.Length == 0)
+ return;
- if (servers.Any() == false)
- {
- // cannot be determined, return null if no config/original url, else use config/original url as base
- // use http or https as appropriate
- return GetBaseUrl(appContext, settings);
- }
-
+ // we have servers, look for this server
foreach (var server in servers)
{
var appId = server.AppId;
var serverName = server.ServerName;
+ // skip if no data
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
- {
continue;
- }
+ // if this server, build and return the url
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
- //match by appId or computer name! return the url configured
- return string.Format("{0}://{1}:{2}/{3}",
+ // match by appId or computer name, return the url configured
+ url = string.Format("{0}://{1}:{2}/{3}",
server.ForceProtocol.IsNullOrWhiteSpace() ? "http" : server.ForceProtocol,
server.ServerAddress,
server.ForcePortnumber.IsNullOrWhiteSpace() ? "80" : server.ForcePortnumber,
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
+
+ appContext.UmbracoApplicationUrl = url.TrimEnd('/');
+ logger.Info("ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using distributedCall/servers)");
}
}
-
- // cannot be determined, return null if no config/original url, else use config/original url as base
- // use http or https as appropriate
- return GetBaseUrl(appContext, settings);
}
///
@@ -113,21 +127,5 @@ namespace Umbraco.Core.Sync
return CurrentServerEnvironmentStatus.Slave;
}
-
- private static string GetBaseUrl(ApplicationContext appContext, IUmbracoSettingsSection settings)
- {
- return (
- // is config empty?
- settings.ScheduledTasks.BaseUrl.IsNullOrWhiteSpace()
- // is the orig req empty?
- ? appContext.OriginalRequestUrl.IsNullOrWhiteSpace()
- // we've got nothing
- ? null
- //the orig req url is not null, use that
- : string.Format("http{0}://{1}", GlobalSettings.UseSSL ? "s" : "", appContext.OriginalRequestUrl)
- // the config has been specified, use that
- : string.Format("http{0}://{1}", GlobalSettings.UseSSL ? "s" : "", settings.ScheduledTasks.BaseUrl))
- .EnsureEndsWith('/');
- }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 82b4636dd4..24bbd61c8b 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -58,11 +58,11 @@
False
- ..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll
+ ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll
False
- ..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll
+ ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll
False
@@ -332,8 +332,12 @@
+
+
+
+
@@ -378,6 +382,7 @@
+
@@ -386,6 +391,7 @@
+
@@ -416,6 +422,7 @@
+
@@ -425,14 +432,16 @@
+
+
-
+
@@ -1102,7 +1111,6 @@
-
diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs
index a53dd09ca5..33c3d58689 100644
--- a/src/Umbraco.Core/UmbracoApplicationBase.cs
+++ b/src/Umbraco.Core/UmbracoApplicationBase.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Web;
using System.Web.Hosting;
+using log4net;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
@@ -31,6 +32,18 @@ namespace Umbraco.Core
///
internal void StartApplication(object sender, EventArgs e)
{
+ //take care of unhandled exceptions - there is nothing we can do to
+ // prevent the entire w3wp process to go down but at least we can try
+ // and log the exception
+ AppDomain.CurrentDomain.UnhandledException += (_, args) =>
+ {
+ var exception = (Exception) args.ExceptionObject;
+ var isTerminating = args.IsTerminating; // always true?
+
+ var msg = "Unhandled exception in AppDomain";
+ if (isTerminating) msg += " (terminating)";
+ Logger.Error(typeof(UmbracoApplicationBase), msg, exception);
+ };
//boot up the application
GetBootManager()
@@ -139,6 +152,9 @@ namespace Umbraco.Core
Logger.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason);
}
OnApplicationEnd(sender, e);
+
+ //Last thing to do is shutdown log4net
+ LogManager.Shutdown();
}
protected abstract IBootManager GetBootManager();
@@ -147,6 +163,7 @@ namespace Umbraco.Core
{
get
{
+ // LoggerResolver can resolve before resolution is frozen
if (LoggerResolver.HasCurrent && LoggerResolver.Current.HasValue)
{
return LoggerResolver.Current.Logger;
diff --git a/src/Umbraco.Core/XmlExtensions.cs b/src/Umbraco.Core/XmlExtensions.cs
index 200b845002..b81785eb7e 100644
--- a/src/Umbraco.Core/XmlExtensions.cs
+++ b/src/Umbraco.Core/XmlExtensions.cs
@@ -317,5 +317,25 @@ namespace Umbraco.Core
}
}
+ // this exists because
+ // new XElement("root", "a\nb").Value is "a\nb" but
+ // .ToString(SaveOptions.*) is "a\r\nb" and cannot figure out how to get rid of "\r"
+ // and when saving data we want nothing to change
+ // this method will produce a string that respects the \r and \n in the data value
+ public static string ToDataString(this XElement xml)
+ {
+ var settings = new XmlWriterSettings
+ {
+ OmitXmlDeclaration = true,
+ NewLineHandling = NewLineHandling.None,
+ Indent = false
+ };
+ var output = new StringBuilder();
+ using (var writer = XmlWriter.Create(output, settings))
+ {
+ xml.WriteTo(writer);
+ }
+ return output.ToString();
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config
index 6ffcf860e6..9edfbb097e 100644
--- a/src/Umbraco.Core/packages.config
+++ b/src/Umbraco.Core/packages.config
@@ -3,8 +3,8 @@
-
-
+
+
diff --git a/src/Umbraco.Tests/ApplicationContextTests.cs b/src/Umbraco.Tests/ApplicationContextTests.cs
index 60eecce81d..61dabf6e3b 100644
--- a/src/Umbraco.Tests/ApplicationContextTests.cs
+++ b/src/Umbraco.Tests/ApplicationContextTests.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Tests
[Test]
public void Is_Configured()
{
- ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", UmbracoVersion.GetSemanticVersion().ToString());
+ ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", UmbracoVersion.GetSemanticVersion().ToSemanticString());
var migrationEntryService = new Mock();
migrationEntryService.Setup(x => x.FindEntry(It.IsAny(), It.IsAny()))
@@ -42,7 +42,7 @@ namespace Umbraco.Tests
[Test]
public void Is_Not_Configured_By_Migration_Not_Found()
{
- ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", UmbracoVersion.GetSemanticVersion().ToString());
+ ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", UmbracoVersion.GetSemanticVersion().ToSemanticString());
var migrationEntryService = new Mock();
migrationEntryService.Setup(x => x.FindEntry(It.IsAny(), It.IsAny()))
diff --git a/src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs b/src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs
deleted file mode 100644
index 2f1a8d99d3..0000000000
--- a/src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using NUnit.Framework;
-using Umbraco.Core;
-using Umbraco.Core.Logging;
-using Umbraco.Tests.TestHelpers;
-using log4net;
-using log4net.Config;
-using log4net.Core;
-using log4net.Layout;
-using log4net.Repository;
-
-namespace Umbraco.Tests
-{
- //Ignore this test, it fails sometimes on the build server - pretty sure it's a threading issue with this test class
- [Ignore]
- [TestFixture]
- public class AsynchronousRollingFileAppenderTests
- {
- private const string ErrorMessage = "TEST ERROR MESSAGE";
- private string _fileFolderPath = @"c:\LogTesting\";
- private readonly Level _errorLevel = Level.Error;
- private AsynchronousRollingFileAppender _appender;
- private ILoggerRepository _rep;
- private Guid _fileGuid;
-
-
-
- private string GetFilePath()
- {
- return string.Format("{0}{1}.log", _fileFolderPath, _fileGuid);
- }
-
- [SetUp]
- public void SetUp()
- {
- _fileFolderPath = TestHelper.MapPathForTest("~/LogTesting/");
-
- _fileGuid = Guid.NewGuid();
- if (File.Exists(GetFilePath()))
- {
- File.Delete(GetFilePath());
- }
-
- _appender = new AsynchronousRollingFileAppender();
- _appender.Threshold = _errorLevel;
- _appender.File = GetFilePath();
- _appender.Layout = new PatternLayout("%d|%-5level|%logger| %message %exception%n");
- _appender.StaticLogFileName = true;
- _appender.AppendToFile = true;
- _appender.ActivateOptions();
-
- _rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
- BasicConfigurator.Configure(_rep, _appender);
- }
-
- [TearDown]
- public void TearDown()
- {
- _rep.Shutdown();
- if (File.Exists(GetFilePath()))
- {
- File.Delete(GetFilePath());
- }
- }
-
- [TestFixtureTearDown]
- public void FixtureTearDown()
- {
- foreach (string file in Directory.GetFiles(_fileFolderPath))
- {
- try
- {
- File.Delete(file);
- }
- catch { }
- }
- }
-
- private void ReleaseFileLocks()
- {
- _rep.Shutdown();
- _appender.Close();
- }
-
- [Test]
- public void CanWriteToFile()
- {
- // Arrange
- ILog log = LogManager.GetLogger(_rep.Name, "CanWriteToDatabase");
-
- // Act
- log.Error(ErrorMessage);
- Thread.Sleep(200); // let background thread finish
-
- // Assert
- ReleaseFileLocks();
- Assert.That(File.Exists(GetFilePath()), Is.True);
- IEnumerable readLines = File.ReadLines(GetFilePath());
- Assert.That(readLines.Count(), Is.GreaterThanOrEqualTo(1));
- }
-
- [Test]
- public void ReturnsQuicklyAfterLogging100Messages()
- {
- // Arrange
- ILog log = LogManager.GetLogger(_rep.Name, "ReturnsQuicklyAfterLogging100Messages");
-
- // Act
- DateTime startTime = DateTime.UtcNow;
- 100.Times(i => log.Error(ErrorMessage));
- DateTime endTime = DateTime.UtcNow;
-
- // Give background thread time to finish
- Thread.Sleep(500);
-
- // Assert
- ReleaseFileLocks();
- Assert.That(endTime - startTime, Is.LessThan(TimeSpan.FromMilliseconds(100)));
- Assert.That(File.Exists(GetFilePath()), Is.True);
- IEnumerable readLines = File.ReadLines(GetFilePath());
- Assert.That(readLines.Count(), Is.GreaterThanOrEqualTo(100));
- }
-
- [Test]
- public void CanLogAtleast1000MessagesASecond()
- {
- // Arrange
- ILog log = LogManager.GetLogger(_rep.Name, "CanLogAtLeast1000MessagesASecond");
-
- int logCount = 0;
- bool logging = true;
- bool logsCounted = false;
-
- var logTimer = new Timer(s =>
- {
- logging = false;
-
- if (File.Exists(GetFilePath()))
- {
- ReleaseFileLocks();
- IEnumerable readLines = File.ReadLines(GetFilePath());
- logCount = readLines.Count();
- }
- logsCounted = true;
- }, null, TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(-1));
-
- // Act
- DateTime startTime = DateTime.UtcNow;
- while (logging)
- {
- log.Error(ErrorMessage);
- }
- TimeSpan testDuration = DateTime.UtcNow - startTime;
-
- while (!logsCounted)
- {
- Thread.Sleep(1);
- }
-
- logTimer.Dispose();
-
- // Assert
- var logsPerSecond = logCount / testDuration.TotalSeconds;
-
- Console.WriteLine("{0} messages logged in {1}s => {2}/s", logCount, testDuration.TotalSeconds, logsPerSecond);
- Assert.That(logsPerSecond, Is.GreaterThan(1000), "Must log at least 1000 messages per second");
- }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs b/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs
new file mode 100644
index 0000000000..37323a67d5
--- /dev/null
+++ b/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using log4net;
+using log4net.Config;
+using log4net.Core;
+using log4net.Layout;
+using log4net.Repository;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Tests.Logging
+{
+ [TestFixture]
+ public class AsyncRollingFileAppenderTest
+ {
+ private const string ErrorMessage = "TEST ERROR MESSAGE";
+ private const string FileFolderPath = @"c:\LogTesting\";
+ private readonly Level ErrorLevel = Level.Error;
+ private AsynchronousRollingFileAppender appender;
+ private ILoggerRepository rep;
+ private Guid fileGuid;
+
+ private string GetFilePath()
+ {
+ return string.Format("{0}{1}.log", FileFolderPath, fileGuid);
+ }
+
+ [SetUp]
+ public void SetUp()
+ {
+ fileGuid = Guid.NewGuid();
+ if (File.Exists(GetFilePath()))
+ {
+ File.Delete(GetFilePath());
+ }
+
+ appender = new AsynchronousRollingFileAppender();
+ appender.Threshold = ErrorLevel;
+ appender.File = GetFilePath();
+ appender.Layout = new PatternLayout("%d|%-5level|%logger| %message %exception%n");
+ appender.StaticLogFileName = true;
+ appender.AppendToFile = true;
+ appender.ActivateOptions();
+
+ rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
+ BasicConfigurator.Configure(rep, appender);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ rep.Shutdown();
+ if (File.Exists(GetFilePath()))
+ {
+ File.Delete(GetFilePath());
+ }
+ }
+
+ [TestFixtureTearDown]
+ public void FixtureTearDown()
+ {
+ foreach (string file in Directory.GetFiles(FileFolderPath))
+ {
+ try
+ {
+ File.Delete(file);
+ }
+ catch { }
+ }
+ }
+
+ private void ReleaseFileLocks()
+ {
+ rep.Shutdown();
+ appender.Close();
+ }
+
+ [Test]
+ public void CanWriteToFile()
+ {
+ // Arrange
+ ILog log = LogManager.GetLogger(rep.Name, "CanWriteToDatabase");
+
+ // Act
+ log.Error(ErrorMessage);
+ Thread.Sleep(200); // let background thread finish
+
+ // Assert
+ ReleaseFileLocks();
+ Assert.That(File.Exists(GetFilePath()), Is.True);
+ IEnumerable readLines = File.ReadLines(GetFilePath());
+ Assert.That(readLines.Count(), Is.GreaterThanOrEqualTo(1));
+ }
+
+ [Test]
+ public void ReturnsQuicklyAfterLogging100Messages()
+ {
+ // Arrange
+ ILog log = LogManager.GetLogger(rep.Name, "ReturnsQuicklyAfterLogging100Messages");
+
+ // Act
+ DateTime startTime = DateTime.UtcNow;
+ 100.Times(i => log.Error(ErrorMessage));
+ DateTime endTime = DateTime.UtcNow;
+
+ // Give background thread time to finish
+ Thread.Sleep(500);
+
+ // Assert
+ ReleaseFileLocks();
+ Assert.That(endTime - startTime, Is.LessThan(TimeSpan.FromMilliseconds(100)));
+ Assert.That(File.Exists(GetFilePath()), Is.True);
+ IEnumerable readLines = File.ReadLines(GetFilePath());
+ Assert.That(readLines.Count(), Is.GreaterThanOrEqualTo(100));
+ }
+
+ [Test]
+ public void CanLogAtleast1000MessagesASecond()
+ {
+ // Arrange
+ ILog log = LogManager.GetLogger(rep.Name, "CanLogAtLeast1000MessagesASecond");
+
+ int logCount = 0;
+ bool logging = true;
+ bool logsCounted = false;
+
+ var logTimer = new Timer(s =>
+ {
+ logging = false;
+
+ if (File.Exists(GetFilePath()))
+ {
+ ReleaseFileLocks();
+ IEnumerable readLines = File.ReadLines(GetFilePath());
+ logCount = readLines.Count();
+ }
+ logsCounted = true;
+ }, null, TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(-1));
+
+ // Act
+ DateTime startTime = DateTime.UtcNow;
+ while (logging)
+ {
+ log.Error(ErrorMessage);
+ }
+ TimeSpan testDuration = DateTime.UtcNow - startTime;
+
+ while (!logsCounted)
+ {
+ Thread.Sleep(1);
+ }
+
+ logTimer.Dispose();
+
+ // Assert
+ var logsPerSecond = logCount / testDuration.TotalSeconds;
+
+ Console.WriteLine("{0} messages logged in {1}s => {2}/s", logCount, testDuration.TotalSeconds, logsPerSecond);
+ Assert.That(logsPerSecond, Is.GreaterThan(1000), "Must log at least 1000 messages per second");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Logging/DebugAppender.cs b/src/Umbraco.Tests/Logging/DebugAppender.cs
new file mode 100644
index 0000000000..a483103992
--- /dev/null
+++ b/src/Umbraco.Tests/Logging/DebugAppender.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using log4net.Appender;
+using log4net.Core;
+
+namespace Umbraco.Tests.Logging
+{
+ internal class DebugAppender : MemoryAppender
+ {
+ public TimeSpan AppendDelay { get; set; }
+ public int LoggedEventCount { get { return m_eventsList.Count; } }
+
+ public bool Cancel { get; set; }
+
+ protected override void Append(LoggingEvent loggingEvent)
+ {
+ if (Cancel) return;
+
+ if (AppendDelay > TimeSpan.Zero)
+ {
+ Thread.Sleep(AppendDelay);
+ }
+ base.Append(loggingEvent);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs
new file mode 100644
index 0000000000..03df34c420
--- /dev/null
+++ b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs
@@ -0,0 +1,326 @@
+using System;
+using System.Diagnostics;
+using System.Security.Principal;
+using System.Threading;
+using log4net;
+using log4net.Appender;
+using log4net.Config;
+using log4net.Core;
+using log4net.Filter;
+using log4net.Repository;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Tests.Logging
+{
+ [TestFixture]
+ public class ParallelForwarderTest : IDisposable
+ {
+ private ParallelForwardingAppender asyncForwardingAppender;
+ private DebugAppender debugAppender;
+ private ILoggerRepository repository;
+ private ILog log;
+
+ [SetUp]
+ public void TestFixtureSetUp()
+ {
+ debugAppender = new DebugAppender();
+ debugAppender.ActivateOptions();
+
+ asyncForwardingAppender = new ParallelForwardingAppender();
+ asyncForwardingAppender.AddAppender(debugAppender);
+ asyncForwardingAppender.ActivateOptions();
+
+ repository = LogManager.CreateRepository(Guid.NewGuid().ToString());
+ BasicConfigurator.Configure(repository, asyncForwardingAppender);
+
+ log = LogManager.GetLogger(repository.Name, "TestLogger");
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ LogManager.Shutdown();
+ }
+
+ [Test]
+ public void CanHandleNullLoggingEvent()
+ {
+ // Arrange
+
+ // Act
+ asyncForwardingAppender.DoAppend((LoggingEvent)null);
+ log.Info("SusequentMessage");
+ asyncForwardingAppender.Close();
+
+ // Assert - should not have had an exception from previous call
+ Assert.That(debugAppender.LoggedEventCount, Is.EqualTo(1), "Expected subsequent message only");
+ Assert.That(debugAppender.GetEvents()[0].MessageObject, Is.EqualTo("SusequentMessage"));
+ }
+
+ [Test]
+ public void CanHandleNullLoggingEvents()
+ {
+ // Arrange
+
+ // Act
+ asyncForwardingAppender.DoAppend((LoggingEvent[])null);
+ log.Info("SusequentMessage");
+ asyncForwardingAppender.Close();
+
+ // Assert - should not have had an exception from previous call
+ Assert.That(debugAppender.LoggedEventCount, Is.EqualTo(1), "Expected subsequent message only");
+ Assert.That(debugAppender.GetEvents()[0].MessageObject, Is.EqualTo("SusequentMessage"));
+ }
+
+ [Test]
+ public void CanHandleAppenderThrowing()
+ {
+ // Arrange
+ var badAppender = new Mock();
+ asyncForwardingAppender.AddAppender(badAppender.Object);
+
+ badAppender
+ .Setup(ba => ba.DoAppend(It.IsAny()))
+ .Throws(new Exception("Bad Appender"));
+ //.Verifiable();
+
+ // Act
+ log.Info("InitialMessage");
+ log.Info("SusequentMessage");
+ asyncForwardingAppender.Close();
+
+ // Assert
+ Assert.That(debugAppender.LoggedEventCount, Is.EqualTo(2));
+ Assert.That(debugAppender.GetEvents()[1].MessageObject, Is.EqualTo("SusequentMessage"));
+ badAppender.Verify(appender => appender.DoAppend(It.IsAny()), Times.Exactly(2));
+ }
+
+ [Test]
+ public void WillLogFastWhenThereIsASlowAppender()
+ {
+ const int testSize = 1000;
+
+ // Arrange
+ debugAppender.AppendDelay = TimeSpan.FromSeconds(10);
+ var watch = new Stopwatch();
+
+ // Act
+ watch.Start();
+ for (int i = 0; i < testSize; i++)
+ {
+ log.Error("Exception");
+ }
+ watch.Stop();
+
+ // Assert
+ Assert.That(debugAppender.LoggedEventCount, Is.EqualTo(0));
+ Assert.That(watch.ElapsedMilliseconds, Is.LessThan(testSize));
+ Console.WriteLine("Logged {0} errors in {1}ms", testSize, watch.ElapsedMilliseconds);
+
+ debugAppender.Cancel = true;
+ }
+
+ [Test]
+ public void WillNotOverflow()
+ {
+ const int testSize = 1000;
+
+ // Arrange
+ debugAppender.AppendDelay = TimeSpan.FromMilliseconds(1);
+ asyncForwardingAppender.BufferSize = 100;
+
+ // Act
+ for (int i = 0; i < testSize; i++)
+ {
+ log.Error("Exception");
+ }
+
+ while (asyncForwardingAppender.BufferEntryCount > 0) ;
+ asyncForwardingAppender.Close();
+
+ // Assert
+ Assert.That(debugAppender.LoggedEventCount, Is.EqualTo(testSize));
+ }
+
+ [Test]
+ public void WillTryToFlushBufferOnShutdown()
+ {
+ const int testSize = 250;
+
+ // Arrange
+ debugAppender.AppendDelay = TimeSpan.FromMilliseconds(1);
+
+ // Act
+ for (int i = 0; i < testSize; i++)
+ {
+ log.Error("Exception");
+ }
+
+ Thread.Sleep(50);
+
+ var numberLoggedBeforeClose = debugAppender.LoggedEventCount;
+ asyncForwardingAppender.Close();
+ var numberLoggedAfterClose = debugAppender.LoggedEventCount;
+
+ // Assert
+ //We can't use specific numbers here because the timing and counts will be different on different systems.
+ Assert.That(numberLoggedBeforeClose, Is.GreaterThan(0), "Some number of Logging events should be logged prior to appender close.");
+ //On some systems, we may not be able to flush all events prior to close, but it is reasonable to assume in this test case
+ //that some events should be logged after close.
+ Assert.That(numberLoggedAfterClose, Is.GreaterThan(numberLoggedBeforeClose), "Some number of LoggingEvents should be logged after close.");
+ Console.WriteLine("Flushed {0} events during shutdown", numberLoggedAfterClose - numberLoggedBeforeClose);
+ }
+
+ [Test, Explicit("Long-running")]
+ public void WillShutdownIfBufferCannotBeFlushedFastEnough()
+ {
+ const int testSize = 250;
+
+ // Arrange
+ debugAppender.AppendDelay = TimeSpan.FromSeconds(1);
+ Stopwatch watch = new Stopwatch();
+
+ // Act
+ for (int i = 0; i < testSize; i++)
+ {
+ log.Error("Exception");
+ }
+
+ Thread.Sleep(TimeSpan.FromSeconds(2));
+ var numberLoggedBeforeClose = debugAppender.LoggedEventCount;
+
+ watch.Start();
+ asyncForwardingAppender.Close();
+ watch.Stop();
+
+ var numberLoggedAfterClose = debugAppender.LoggedEventCount;
+
+ // Assert
+ Assert.That(numberLoggedBeforeClose, Is.GreaterThan(0));
+ Assert.That(numberLoggedAfterClose, Is.GreaterThan(numberLoggedBeforeClose));
+ Assert.That(numberLoggedAfterClose, Is.LessThan(testSize));
+ //We can't assume what the shutdown time will be. It will vary from system to system. Don't test shutdown time.
+ var events = debugAppender.GetEvents();
+ var evnt = events[events.Length - 1];
+ Assert.That(evnt.MessageObject, Is.EqualTo("The buffer was not able to be flushed before timeout occurred."));
+ Console.WriteLine("Flushed {0} events during shutdown which lasted {1}ms", numberLoggedAfterClose - numberLoggedBeforeClose, watch.ElapsedMilliseconds);
+ }
+
+ [Test]
+ public void ThreadContextPropertiesArePreserved()
+ {
+ // Arrange
+ ThreadContext.Properties["TestProperty"] = "My Value";
+ Assert.That(asyncForwardingAppender.Fix & FixFlags.Properties, Is.EqualTo(FixFlags.Properties), "Properties must be fixed if they are to be preserved");
+
+ // Act
+ log.Info("Information");
+ asyncForwardingAppender.Close();
+
+ // Assert
+ var lastLoggedEvent = debugAppender.GetEvents()[0];
+ Assert.That(lastLoggedEvent.Properties["TestProperty"], Is.EqualTo("My Value"));
+ }
+
+ [Test]
+ public void MessagesExcludedByFilterShouldNotBeAppended()
+ {
+ // Arrange
+ var levelFilter =
+ new LevelRangeFilter
+ {
+ LevelMin = Level.Warn,
+ LevelMax = Level.Error,
+ };
+
+ asyncForwardingAppender.AddFilter(levelFilter);
+
+ // Act
+ log.Info("Info");
+ log.Warn("Warn");
+ log.Error("Error");
+ log.Fatal("Fatal");
+
+ asyncForwardingAppender.Close();
+
+ //Assert
+ Assert.That(debugAppender.LoggedEventCount, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void HelperCanGenerateLoggingEventWithAllProperties()
+ {
+ // Arrange
+ var helper = new LoggingEventHelper("TestLoggerName", FixFlags.All);
+ ThreadContext.Properties["MyProperty"] = "MyValue";
+ var exception = new Exception("SomeError");
+
+ var stackFrame = new StackFrame(0);
+ var currentUser = WindowsIdentity.GetCurrent();
+ var loggingTime = DateTime.Now; // Log4Net does not seem to be using UtcNow
+
+ // Act
+ var loggingEvent = helper.CreateLoggingEvent(Level.Emergency, "Who's on live support?", exception);
+ Thread.Sleep(50); // to make sure the time stamp is actually captured
+
+ // Assert
+ Assert.That(loggingEvent.Domain, Is.EqualTo(AppDomain.CurrentDomain.FriendlyName), "Domain");
+ //The identity assigned to new threads is dependent upon AppDomain principal policy.
+ //Background information here:http://www.neovolve.com/post/2010/10/21/Unit-testing-a-workflow-that-relies-on-ThreadCurrentPrincipalIdentityName.aspx
+ //VS2013 does have a principal assigned to new threads in the unit test.
+ //It's probably best not to test that the identity has been set.
+ //Assert.That(loggingEvent.Identity, Is.Empty, "Identity: always empty for some reason");
+ Assert.That(loggingEvent.UserName, Is.EqualTo(currentUser == null ? String.Empty : currentUser.Name), "UserName");
+ Assert.That(loggingEvent.ThreadName, Is.EqualTo(Thread.CurrentThread.Name), "ThreadName");
+
+ Assert.That(loggingEvent.Repository, Is.Null, "Repository: Helper does not have access to this");
+ Assert.That(loggingEvent.LoggerName, Is.EqualTo("TestLoggerName"), "LoggerName");
+
+ Assert.That(loggingEvent.Level, Is.EqualTo(Level.Emergency), "Level");
+ //Raised time to within 10 ms. However, this may not be a valid test. The time is going to vary from system to system. The
+ //tolerance setting here is arbitrary.
+ Assert.That(loggingEvent.TimeStamp, Is.EqualTo(loggingTime).Within(TimeSpan.FromMilliseconds(10)), "TimeStamp");
+ Assert.That(loggingEvent.ExceptionObject, Is.EqualTo(exception), "ExceptionObject");
+ Assert.That(loggingEvent.MessageObject, Is.EqualTo("Who's on live support?"), "MessageObject");
+
+ Assert.That(loggingEvent.LocationInformation.MethodName, Is.EqualTo(stackFrame.GetMethod().Name), "LocationInformation");
+ Assert.That(loggingEvent.Properties["MyProperty"], Is.EqualTo("MyValue"), "Properties");
+ }
+
+ private bool _disposed = false;
+
+ //Implement IDisposable.
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ if (asyncForwardingAppender != null)
+ {
+ asyncForwardingAppender.Dispose();
+ asyncForwardingAppender = null;
+ }
+ }
+ // Free your own state (unmanaged objects).
+ // Set large fields to null.
+ _disposed = true;
+ }
+ }
+
+ // Use C# destructor syntax for finalization code.
+ ~ParallelForwarderTest()
+ {
+ // Simply call Dispose(false).
+ Dispose(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Logging/RingBufferTest.cs b/src/Umbraco.Tests/Logging/RingBufferTest.cs
new file mode 100644
index 0000000000..8e80faf8ad
--- /dev/null
+++ b/src/Umbraco.Tests/Logging/RingBufferTest.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Umbraco.Core.Logging;
+using Umbraco.Tests.TestHelpers;
+
+namespace Umbraco.Tests.Logging
+{
+ [TestFixture]
+ public class RingBufferTest
+ {
+ [Test]
+ public void PerfTest()
+ {
+ RingBuffer ringBuffer = new RingBuffer(1000);
+ Stopwatch ringWatch = new Stopwatch();
+ ringWatch.Start();
+ for (int i = 0; i < 1000000; i++)
+ {
+ ringBuffer.Enqueue("StringOfFun");
+ }
+ ringWatch.Stop();
+
+ Assert.That(ringWatch.ElapsedMilliseconds, Is.LessThan(150));
+ }
+
+ [Test]
+ public void PerfTestThreads()
+ {
+ RingBuffer ringBuffer = new RingBuffer(1000);
+
+ Stopwatch ringWatch = new Stopwatch();
+ List ringTasks = new List();
+ CancellationTokenSource cancelationTokenSource = new CancellationTokenSource();
+ CancellationToken cancelationToken = cancelationTokenSource.Token;
+ for (int t = 0; t < 10; t++)
+ {
+ ringTasks.Add(new Task(() =>
+ {
+ for (int i = 0; i < 1000000; i++)
+ {
+ ringBuffer.Enqueue("StringOfFun");
+ }
+ }, cancelationToken));
+ }
+ ringWatch.Start();
+ ringTasks.ForEach(t => t.Start());
+ var allTasks = ringTasks.ToArray();
+ Task.WaitAny(allTasks);
+ ringWatch.Stop();
+ //Cancel tasks to avoid System.AppDominUnloadException which is caused when the domain is unloaded
+ //and threads created in the domain are not stopped.
+ //Do this before assertions because they may throw an exception causing the thread cancellation to not happen.
+ cancelationTokenSource.Cancel();
+ try
+ {
+ Task.WaitAll(allTasks);
+ }
+ catch (AggregateException)
+ {
+ //Don't care about cancellation Exceptions.
+ }
+ //Tolerance at 500 was too low
+ ringTasks.ForEach(t => t.Dispose());
+ Assert.That(ringWatch.ElapsedMilliseconds, Is.LessThan(1000));
+ }
+
+ [Test]
+ public void PerfTestThreadsWithDequeues()
+ {
+ RingBuffer ringBuffer = new RingBuffer(1000);
+
+ Stopwatch ringWatch = new Stopwatch();
+ List ringTasks = new List();
+ CancellationTokenSource cancelationTokenSource = new CancellationTokenSource();
+ CancellationToken cancelationToken = cancelationTokenSource.Token;
+ for (int t = 0; t < 10; t++)
+ {
+ ringTasks.Add(new Task(() =>
+ {
+ for (int i = 0; i < 1000000; i++)
+ {
+ ringBuffer.Enqueue("StringOfFun");
+ }
+ }, cancelationToken));
+ }
+ for (int t = 0; t < 10; t++)
+ {
+ ringTasks.Add(new Task(() =>
+ {
+ for (int i = 0; i < 1000000; i++)
+ {
+ string foo;
+ ringBuffer.TryDequeue(out foo);
+ }
+ }));
+ }
+ ringWatch.Start();
+ ringTasks.ForEach(t => t.Start());
+ var allTasks = ringTasks.ToArray();
+ Task.WaitAny(allTasks);
+ ringWatch.Stop();
+ //Cancel tasks to avoid System.AppDominUnloadException which is caused when the domain is unloaded
+ //and threads created in the domain are not stopped.
+ //Do this before assertions because they may throw an exception causing the thread cancellation to not happen.
+ cancelationTokenSource.Cancel();
+ try
+ {
+ Task.WaitAll(allTasks);
+ }
+ catch (AggregateException)
+ {
+ //Don't care about cancellation Exceptions.
+ }
+ ringTasks.ForEach(t => t.Dispose());
+ Assert.That(ringWatch.ElapsedMilliseconds, Is.LessThan(800));
+ }
+
+ [Test]
+ public void WhenRingSizeLimitIsHit_ItemsAreDequeued()
+ {
+ // Arrange
+ const int limit = 2;
+ object object1 = "one";
+ object object2 = "two";
+ object object3 = "three";
+ RingBuffer queue = new RingBuffer(limit);
+
+ // Act
+ queue.Enqueue(object1);
+ queue.Enqueue(object2);
+ queue.Enqueue(object3);
+
+ // Assert
+ object value;
+ queue.TryDequeue(out value);
+ Assert.That(value, Is.EqualTo(object2));
+ queue.TryDequeue(out value);
+ Assert.That(value, Is.EqualTo(object3));
+ queue.TryDequeue(out value);
+ Assert.That(value, Is.Null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs
index 609452978c..c786a61996 100644
--- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs
+++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs
@@ -148,7 +148,7 @@ namespace Umbraco.Tests.Membership
Assert.AreEqual(false, provider.EnablePasswordReset);
Assert.AreEqual(false, provider.RequiresQuestionAndAnswer);
Assert.AreEqual(true, provider.RequiresUniqueEmail);
- Assert.AreEqual(20, provider.MaxInvalidPasswordAttempts);
+ Assert.AreEqual(5, provider.MaxInvalidPasswordAttempts);
Assert.AreEqual(10, provider.PasswordAttemptWindow);
Assert.AreEqual(provider.DefaultMinPasswordLength, provider.MinRequiredPasswordLength);
Assert.AreEqual(provider.DefaultMinNonAlphanumericChars, provider.MinRequiredNonAlphanumericCharacters);
diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql b/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql
index 818a20babb..49af66b93b 100644
--- a/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql
+++ b/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql
@@ -478,14 +478,6 @@ permission char (1) NOT NULL
;
ALTER TABLE umbracoUser2NodePermission ADD CONSTRAINT PK_umbracoUser2NodePermission PRIMARY KEY CLUSTERED (userId, nodeId, permission)
-;
-CREATE TABLE umbracoUserLogins
-(
-contextId CHAR(36) NOT NULL,
-userID int NOT NULL,
-timeout bigint NOT NULL
-)
-
;
INSERT INTO umbracoNode (id, trashed, parentID, nodeUser, level, path, sortOrder, uniqueID, text, nodeObjectType, createDate) VALUES
(-92, 0, -1, 0, 11, '-1,-92', 37, 'f0bc4bfb-b499-40d6-ba86-058885a5178c', 'Label', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),
diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql b/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql
index 82f4eefcca..e4c3d47ee2 100644
Binary files a/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql and b/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql differ
diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/SqlCeTotal-480.sql b/src/Umbraco.Tests/Migrations/SqlScripts/SqlCeTotal-480.sql
index 84a6d0927a..9f535a518a 100644
--- a/src/Umbraco.Tests/Migrations/SqlScripts/SqlCeTotal-480.sql
+++ b/src/Umbraco.Tests/Migrations/SqlScripts/SqlCeTotal-480.sql
@@ -443,14 +443,7 @@ CREATE TABLE [umbracoUser2NodePermission]
;
ALTER TABLE [umbracoUser2NodePermission] ADD CONSTRAINT [PK_umbracoUser2NodePermission] PRIMARY KEY ([userId], [nodeId], [permission])
;
-CREATE TABLE [umbracoUserLogins]
-(
-[contextID] [uniqueidentifier] NOT NULL,
-[userID] [int] NOT NULL,
-[timeout] [bigint] NOT NULL
-)
-;
ALTER TABLE [umbracoAppTree] ADD
CONSTRAINT [FK_umbracoAppTree_umbracoApp] FOREIGN KEY ([appAlias]) REFERENCES [umbracoApp] ([appAlias])
;
diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql b/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql
index ff82b7a3fc..c729a7ffbc 100644
--- a/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql
+++ b/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql
@@ -541,16 +541,7 @@ CREATE TABLE [umbracoUser2NodePermission]
;
ALTER TABLE [umbracoUser2NodePermission] ADD CONSTRAINT [PK_umbracoUser2NodePermission] PRIMARY KEY CLUSTERED ([userId], [nodeId], [permission])
-;
-CREATE TABLE [umbracoUserLogins]
-(
-[contextID] [uniqueidentifier] NOT NULL,
-[userID] [int] NOT NULL,
-[timeout] [bigint] NOT NULL
-)
;
-CREATE CLUSTERED INDEX umbracoUserLogins_Index ON umbracoUserLogins (contextID)
-;
ALTER TABLE [umbracoAppTree] ADD
CONSTRAINT [FK_umbracoAppTree_umbracoApp] FOREIGN KEY ([appAlias]) REFERENCES [umbracoApp] ([appAlias])
;
diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs
index 95f3f55308..ad47bafb80 100644
--- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs
+++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs
@@ -69,7 +69,7 @@ namespace Umbraco.Tests.Models.Mapping
}
[Test]
- public void ContentTypeDisplay_To_PropertyType()
+ public void PropertyTypeDisplay_To_PropertyType()
{
// setup the mocks to return the data we want to test against...
@@ -207,6 +207,33 @@ namespace Umbraco.Tests.Models.Mapping
}
}
+ [Test]
+ public void ContentTypeDisplay_With_Composition_To_IContentType()
+ {
+ //Arrange
+
+ // setup the mocks to return the data we want to test against...
+
+ _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny()))
+ .Returns(Mock.Of