Merge branch 'dev-v7-contenttypeeditor' of https://github.com/umbraco/Umbraco-CMS into dev-v7-contenttypeeditor

This commit is contained in:
Mads Rasmussen
2015-07-13 19:38:30 +02:00
180 changed files with 4805 additions and 1982 deletions

1
.gitignore vendored
View File

@@ -129,3 +129,4 @@ src/*.boltdata/
/src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.front.js
src/umbraco.sln.ide/*
build/UmbracoCms.*/
src/.vs/

View File

@@ -68,6 +68,12 @@
<PropertyGroup Condition="'$(BUILD_RELEASE)'!='' AND '$(BUILD_COMMENT)'!=''">
<DECIMAL_BUILD_NUMBER>.$(BUILD_RELEASE)-$(BUILD_COMMENT)</DECIMAL_BUILD_NUMBER>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_RELEASE)'!='' AND '$(BUILD_NIGHTLY)'!=''">
<DECIMAL_BUILD_NUMBER>.$(BUILD_RELEASE)-$(BUILD_NIGHTLY)</DECIMAL_BUILD_NUMBER>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_RELEASE)'!='' AND '$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''">
<DECIMAL_BUILD_NUMBER>.$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)</DECIMAL_BUILD_NUMBER>
</PropertyGroup>
<PropertyGroup>
<BuildConfiguration>Release</BuildConfiguration>
@@ -271,9 +277,11 @@
<PropertyGroup>
<NewVersion>$(BUILD_RELEASE)</NewVersion>
<NewVersion Condition="'$(BUILD_COMMENT)'!=''">$(BUILD_RELEASE)-$(BUILD_COMMENT)</NewVersion>
<NewVersion Condition="'$(BUILD_NIGHTLY)'!=''">$(BUILD_RELEASE)-$(BUILD_NIGHTLY)</NewVersion>
<NewVersion Condition="'$(BUILD_COMMENT)'!='' And '$(BUILD_NIGHTLY)'!=''">$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)</NewVersion>
</PropertyGroup>
<!-- Match & replace 3 and 4 digit version numbers and -beta (if it's there) -->
<!-- Match & replace 3 and 4 digit version numbers and -beta and +nightly (if they're there) -->
<FileUpdate
Files="..\src\Umbraco.Core\Configuration\UmbracoVersion.cs"
Regex="(\d+)\.(\d+)\.(\d+)(.(\d+))?"
@@ -283,6 +291,16 @@
Regex="CurrentComment { get { return &quot;(.+)?&quot;"
ReplacementText="CurrentComment { get { return &quot;$(BUILD_COMMENT)&quot;"/>
<FileUpdate Files="..\src\Umbraco.Core\Configuration\UmbracoVersion.cs"
Condition="'$(BUILD_NIGHTLY)'!=''"
Regex="CurrentComment { get { return &quot;(.+)?&quot;"
ReplacementText="CurrentComment { get { return &quot;$(BUILD_NIGHTLY)&quot;"/>
<FileUpdate Files="..\src\Umbraco.Core\Configuration\UmbracoVersion.cs"
Condition="'$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''"
Regex="CurrentComment { get { return &quot;(.+)?&quot;"
ReplacementText="CurrentComment { get { return &quot;$(BUILD_COMMENT)-$(BUILD_NIGHTLY)&quot;"/>
<!--This updates the AssemblyFileVersion for the solution to the umbraco version-->
<FileUpdate
Files="..\src\SolutionInfo.cs"
@@ -300,10 +318,24 @@
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)&quot;)"/>
<FileUpdate
Condition="'$(BUILD_NIGHTLY)'!=''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)-$(BUILD_NIGHTLY)&quot;)"/>
<FileUpdate
Condition="'$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)&quot;)"/>
<FileUpdate
Condition="'$(BUILD_COMMENT)'=='' AND '$(BUILD_NIGHTLY)'==''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)&quot;)"/>
<!--This updates the copyright year-->
<FileUpdate
Condition="'$(BUILD_COMMENT)'==''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyCopyright\(&quot;Copyright © Umbraco (\d{4})&quot;\)"
ReplacementText="AssemblyCopyright(&quot;Copyright © Umbraco $([System.DateTime]::Now.ToString(`yyyy`))&quot;)"/>

View File

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

View File

@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.3.0")]
[assembly: AssemblyInformationalVersion("7.3.0-beta")]
[assembly: AssemblyInformationalVersion("7.3.0-beta2")]

View File

@@ -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
}
/// <summary>
/// The original/first url that the web application executes
/// The application url.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
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<bool> _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<ApplicationContext>("The migration for version: '" + currentVersion + " has not been executed, there is no record in the database");
ProfilingLogger.Logger.Debug<ApplicationContext>(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<ApplicationContext>("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
ProfilingLogger.Logger.Debug<ApplicationContext>(string.Format("CurrentVersion different from configStatus: '{0}','{1}'", currentVersion.ToSemanticString(), configStatus));
}
return ok;

View File

@@ -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<IDisposable> 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<IDisposable> 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<object>();
var callbackHandleInitLock = new object();
@@ -197,7 +210,7 @@ namespace Umbraco.Core
}
},
/*state:*/ null,
/*millisecondsTimeOutInterval:*/ Timeout.Infinite,
/*millisecondsTimeOutInterval:*/ millisecondsTimeout,
/*executeOnlyOnce:*/ true);
}

View File

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

View File

@@ -11,6 +11,8 @@
bool DisableFindContentByIdPath { get; }
string UrlProviderMode { get; }
string UmbracoApplicationUrl { get; }
}
}

View File

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

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
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

View File

@@ -27,6 +27,7 @@ namespace Umbraco.Core
/// <summary>
/// The root id for all top level dictionary items
/// </summary>
[Obsolete("There is no dictionary root item id anymore, it is simply null")]
public const string DictionaryItemRootId = "41c7638d-f529-4bff-853e-59a0c2fb1bde";
}

View File

@@ -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";
/// <summary>
/// The prefix used for external identity providers for their authentication type

View File

@@ -76,7 +76,9 @@ namespace Umbraco.Core
_profilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler);
_timer = _profilingLogger.TraceDuration<CoreBootManager>("Umbraco application starting", "Umbraco application startup complete");
_timer = _profilingLogger.TraceDuration<CoreBootManager>(
string.Format("Umbraco application ({0}) starting", UmbracoVersion.GetSemanticVersion().ToSemanticString()),
"Umbraco application startup complete");
CreateApplicationCache();

View File

@@ -0,0 +1,105 @@
using System;
using log4net.Appender;
using log4net.Core;
using log4net.Util;
namespace Umbraco.Core.Logging
{
/// <remarks>
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
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); }
}
/// <summary>
/// The logger name that will be used for logging internal errors.
/// </summary>
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
}
}

View File

@@ -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
{
/// <summary>
/// Based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/
/// <summary>
/// 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/
/// </summary>
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<LoggingEvent> _pendingAppends;
private int _queueSizeLimit = 1000;
private bool _shuttingDown;
private RingBuffer<LoggingEvent> 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<LoggingEvent>(QueueSizeLimit);
pendingAppends.BufferOverflow += OnBufferOverflow;
StartAppendTask();
}
public override void ActivateOptions()
{
base.ActivateOptions();
_pendingAppends = new RingBuffer<LoggingEvent>(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<T>
{
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<T> : IQueue<T>
{
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<T>
{
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<object, EventArgs> BufferOverflow;
public event Action<object, EventArgs> 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;
}
}
}
}

View File

@@ -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
///</summary>
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;
}
/// <summary>
@@ -53,31 +59,19 @@ namespace Umbraco.Core.Logging
return LogManager.GetLogger(getTypeFromInstance.GetType());
}
/// <summary>
/// Useful if the logger itself is running on another thread
/// </summary>
/// <param name="generateMessageFormat"></param>
/// <returns></returns>
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<object>[] 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<object>[] 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);
}
/// <summary>
@@ -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()));
}
/// <summary>
@@ -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()));
}
/// <summary>
@@ -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);
}

View File

@@ -0,0 +1,17 @@
using log4net.Core;
namespace Umbraco.Core.Logging
{
/// <remarks>
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
internal class LoggingEventContext
{
public LoggingEventContext(LoggingEvent loggingEvent)
{
LoggingEvent = loggingEvent;
}
public LoggingEvent LoggingEvent { get; set; }
}
}

View File

@@ -0,0 +1,31 @@
using System;
using log4net.Core;
namespace Umbraco.Core.Logging
{
/// <remarks>
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
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;
}
}
}

View File

@@ -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
{
/// <summary>
/// An asynchronous appender based on <see cref="BlockingCollection{T}"/>
/// </summary>
/// <remarks>
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
public class ParallelForwardingAppender : AsyncForwardingAppenderBase, IDisposable
{
#region Private Members
private const int DefaultBufferSize = 1000;
private BlockingCollection<LoggingEventContext> _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
/// <summary>
/// Gets or sets the number of LoggingEvents that will be buffered. Set to null for unlimited.
/// </summary>
public override int? BufferSize
{
get { return _bufferSize; }
set { _bufferSize = value; }
}
public int BufferEntryCount
{
get
{
if (_loggingEvents == null) return 0;
return _loggingEvents.Count;
}
}
/// <summary>
/// Gets or sets the time period in which the system will wait for appenders to flush before canceling the background task.
/// </summary>
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<LoggingEventContext>(BufferSize.Value);
}
else
{
//No limit on the number of events.
_loggingEvents = new BlockingCollection<LoggingEventContext>();
}
//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
/// <summary>
/// Iterates over a BlockingCollection containing LoggingEvents.
/// </summary>
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
}
}

View File

@@ -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<IDictionaryTranslation> _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<IDictionaryTranslation>();
}
private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, Guid>(x => x.ParentId);
private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, Guid?>(x => x.ParentId);
private static readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, string>(x => x.ItemKey);
private static readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, IEnumerable<IDictionaryTranslation>>(x => x.Translations);
@@ -37,7 +37,7 @@ namespace Umbraco.Core.Models
/// Gets or Sets the Parent Id of the Dictionary Item
/// </summary>
[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();
}
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Models
/// Gets or Sets the Parent Id of the Dictionary Item
/// </summary>
[DataMember]
Guid ParentId { get; set; }
Guid? ParentId { get; set; }
/// <summary>
/// Gets or sets the Key for the Dictionary Item

View File

@@ -12,6 +12,13 @@ namespace Umbraco.Core.Models.Identity
public class BackOfficeIdentityUser : IdentityUser<int, IIdentityUserLogin, IdentityUserRole<string>, IdentityUserClaim<int>>
{
public BackOfficeIdentityUser()
{
StartMediaId = -1;
StartContentId = -1;
Culture = Configuration.GlobalSettings.DefaultUILanguage;
}
public virtual async Task<ClaimsIdentity> 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; }
/// <summary>
/// Lockout is always enabled
/// </summary>
public override bool LockoutEnabled
{
get { return true; }
set
{
//do nothing
}
}
/// <summary>
/// Based on the user's lockout end date, this will determine if they are locked out
/// </summary>
internal bool IsLockedOut
{
get
{
var isLocked = (LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now);
return isLocked;
}
}
/// <summary>
/// Overridden to make the retrieval lazy
/// </summary>

View File

@@ -14,8 +14,7 @@ namespace Umbraco.Core.Models.Identity
config.CreateMap<IUser, BackOfficeIdentityUser>()
.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()));
}

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Core.Models
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class Macro : Entity, IMacro
public class Macro : Entity, IMacro
{
public Macro()
{

View File

@@ -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<int>(() => this.Id);
}
_propertyTypes.CollectionChanged += PropertyTypesChanged;
}
}

View File

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

View File

@@ -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")]

View File

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

View File

@@ -112,7 +112,7 @@ namespace Umbraco.Core.ObjectResolution
/// <exception cref="InvalidOperationException">resolution is already frozen.</exception>
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;
}
}
/// <summary>

View File

@@ -151,6 +151,13 @@ namespace Umbraco.Core.Persistence
_db.Update<UserDto>("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<Database>(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<Database>(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<Database>(string.Format("Create Index sql {0}:\n {1}", createdIndex, sql));
}
transaction.Complete();
}

View File

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

View File

@@ -53,5 +53,32 @@ namespace Umbraco.Core.Persistence.Migrations
/// to ensure they are not executed twice.
/// </summary>
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());
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<IContent> _contentPreviewRepository;
private readonly ContentXmlRepository<IContent> _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<NodeDto>("WHERE id = @ParentId", new { ParentId = entity.ParentId });
int level = parent.Level + 1;
var maxSortOrder =
Database.ExecuteScalar<int>(
"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<int>(
"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

View File

@@ -24,15 +24,18 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
/// <remarks>Exposes shared functionality</remarks>
/// <typeparam name="TEntity"></typeparam>
internal abstract class ContentTypeBaseRepository<TEntity> : PetaPocoRepositoryBase<int, TEntity>
internal abstract class ContentTypeBaseRepository<TEntity> : PetaPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
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;
/// <summary>
/// The container object type - used for organizing content types
/// </summary>
@@ -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<ContentTypeDto>("WHERE alias = @Alias", new {Alias = composition.Alias});
var contentTypeDto = Database.FirstOrDefault<ContentTypeDto>("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<ContentTypeDto>("WHERE nodeId = @Id", new {Id = entity.Id});
var dtoPk = Database.First<ContentTypeDto>("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<ContentType2ContentTypeDto>("WHERE childContentTypeId = @Id", new {Id = entity.Id});
Database.Delete<ContentType2ContentTypeDto>("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<PropertyTypeDto>("WHERE contentTypeId = @Id", new {Id = key});
var propertyTypes = Database.Fetch<PropertyTypeDto>("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<ContentTypeAllowedContentTypeDto>("WHERE Id = @Id", new {Id = entity.Id});
Database.Delete<ContentTypeAllowedContentTypeDto>("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<PropertyTypeDto>("WHERE contentTypeId = @Id", new {Id = entity.Id});
var dbPropertyTypes = Database.Fetch<PropertyTypeDto>("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<TagRelationshipDto>("WHERE propertyTypeId = @Id", new { Id = item });
Database.Delete<PropertyDataDto>("WHERE propertytypeid = @Id", new {Id = item});
Database.Delete<PropertyDataDto>("WHERE propertytypeid = @Id", new { Id = item });
Database.Delete<PropertyTypeDto>("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<PropertyTypeGroupDto>("WHERE contenttypeNodeId = @Id", new {Id = entity.Id})
Database.Fetch<PropertyTypeGroupDto>("WHERE contenttypeNodeId = @Id", new { Id = entity.Id })
.Select(x => new Tuple<int, string>(x.Id, x.Text))
.ToList();
var entityPropertyGroups = entity.PropertyGroups.Select(x => new Tuple<int, string>(x.Id, x.Name)).ToList();
@@ -389,11 +392,11 @@ AND umbracoNode.id <> @id",
foreach (var tab in tabs)
{
Database.Update<PropertyTypeDto>("SET propertyTypeGroupId = NULL WHERE propertyTypeGroupId = @PropertyGroupId",
new {PropertyGroupId = tab.Item1});
new { PropertyGroupId = tab.Item1 });
Database.Update<PropertyTypeGroupDto>("SET parentGroupId = NULL WHERE parentGroupId = @TabId",
new {TabId = tab.Item1});
new { TabId = tab.Item1 });
Database.Delete<PropertyTypeGroupDto>("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<PropertyType>();
foreach (var dto in dtos.Where(x => (x.PropertyTypeGroupId > 0) == false))
{
var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse<DataTypeDatabaseType>(true), dto.Alias);
var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse<DataTypeDatabaseType>(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;
});
}
/// <summary>
/// Try to set the data type id based on its ControlId
/// </summary>
@@ -621,28 +624,30 @@ AND umbracoNode.id <> @id",
}
}
public static IEnumerable<IMediaType> GetMediaTypes<TRepo>(
int[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
public static IEnumerable<IMediaType> GetMediaTypes<TRepo, TId>(
TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
TRepo contentTypeRepository)
where TRepo : IRepositoryQueryable<int, TEntity>
where TRepo : IReadRepository<TId, TEntity>
where TId: struct
{
IDictionary<int, IEnumerable<int>> allParentMediaTypeIds;
IDictionary<TId, IEnumerable<TId>> allParentMediaTypeIds;
var mediaTypes = MapMediaTypes(mediaTypeIds, db, sqlSyntax, out allParentMediaTypeIds)
.ToArray();
MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds);
return mediaTypes;
}
public static IEnumerable<IContentType> GetContentTypes<TRepo>(
int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
public static IEnumerable<IContentType> GetContentTypes<TRepo, TId>(
TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
TRepo contentTypeRepository,
ITemplateRepository templateRepository)
where TRepo : IRepositoryQueryable<int, TEntity>
where TRepo : IReadRepository<TId, TEntity>
where TId : struct
{
IDictionary<int, IEnumerable<AssociatedTemplate>> allAssociatedTemplates;
IDictionary<int, IEnumerable<int>> allParentContentTypeIds;
IDictionary<TId, IEnumerable<AssociatedTemplate>> allAssociatedTemplates;
IDictionary<TId, IEnumerable<TId>> 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<TRepo>(IContentTypeComposition[] contentTypes,
internal static void MapContentTypeChildren<TRepo, TId>(IContentTypeComposition[] contentTypes,
Database db, ISqlSyntaxProvider sqlSyntax,
TRepo contentTypeRepository,
IDictionary<int, IEnumerable<int>> allParentContentTypeIds)
where TRepo : IRepositoryQueryable<int, TEntity>
IDictionary<TId, IEnumerable<TId>> allParentContentTypeIds)
where TRepo : IReadRepository<TId, TEntity>
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<TRepo>(IContentType[] contentTypes,
internal static void MapContentTypeTemplates<TRepo, TId>(IContentType[] contentTypes,
Database db,
TRepo contentTypeRepository,
ITemplateRepository templateRepository,
IDictionary<int, IEnumerable<AssociatedTemplate>> associatedTemplates)
where TRepo : IRepositoryQueryable<int, TEntity>
IDictionary<TId, IEnumerable<AssociatedTemplate>> associatedTemplates)
where TRepo : IReadRepository<TId, TEntity>
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<ITemplate>()).ToArray();
}
}
internal static IEnumerable<IMediaType> MapMediaTypes(int[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
out IDictionary<int, IEnumerable<int>> parentMediaTypeIds)
internal static IEnumerable<IMediaType> MapMediaTypes<TId>(TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
out IDictionary<TId, IEnumerable<TId>> 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<IMediaType>();
}
parentMediaTypeIds = new Dictionary<int, IEnumerable<int>>();
parentMediaTypeIds = new Dictionary<TId, IEnumerable<TId>>();
var mappedMediaTypes = new List<IMediaType>();
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<IContentType> MapContentTypes(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
out IDictionary<int, IEnumerable<AssociatedTemplate>> associatedTemplates,
out IDictionary<int, IEnumerable<int>> parentContentTypeIds)
internal static IEnumerable<IContentType> MapContentTypes<TId>(TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
out IDictionary<TId, IEnumerable<AssociatedTemplate>> associatedTemplates,
out IDictionary<TId, IEnumerable<TId>> 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<IContentType>();
}
parentContentTypeIds = new Dictionary<int, IEnumerable<int>>();
associatedTemplates = new Dictionary<int, IEnumerable<AssociatedTemplate>>();
parentContentTypeIds = new Dictionary<TId, IEnumerable<TId>>();
associatedTemplates = new Dictionary<TId, IEnumerable<AssociatedTemplate>>();
var mappedContentTypes = new List<IContentType>();
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<bool?, int?>(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>(T contentType, int currentCtId, List<dynamic> result, IDictionary<int, IEnumerable<int>> parentContentTypeIds)
where T: IContentTypeBase
private static void MapCommonContentTypeObjects<T, TId>(T contentType, TId currentCtId, List<dynamic> result, IDictionary<TId, IEnumerable<TId>> 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<int?, int?, string>(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<int, PropertyTypeCollection> allPropertyTypeCollection,
out IDictionary<int, PropertyGroupCollection> 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;
}
}
}
/// <summary>
/// Inner repository to support the GUID lookups and keep the caching consistent
/// </summary>
internal class GuidReadOnlyContentTypeBaseRepository : PetaPocoRepositoryBase<Guid, TEntity>
{
private readonly ContentTypeBaseRepository<TEntity> _parentRepo;
public GuidReadOnlyContentTypeBaseRepository(
ContentTypeBaseRepository<TEntity> 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<TEntity> 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<TEntity> PerformGetByQuery(IQuery<TEntity> query)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> 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<TEntity> PerformGetAll(params Guid[] ids);
/// <summary>
/// Gets an Entity by Id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public TEntity Get(Guid id)
{
return _guidRepo.Get(id);
}
/// <summary>
/// Gets all entities of the spefified type
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public IEnumerable<TEntity> GetAll(params Guid[] ids)
{
return _guidRepo.GetAll(ids);
}
/// <summary>
/// Boolean indicating whether an Entity with the specified Id exists
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool Exists(Guid id)
{
return _guidRepo.Exists(id);
}
}
}

View File

@@ -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<IContentType> PerformGetAll(params Guid[] ids)
{
if (ids.Any())
{
return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository);
}
else
{
var sql = new Sql().Select("id").From<NodeDto>(SqlSyntax).Where<NodeDto>(dto => dto.NodeObjectType == NodeObjectTypeId);
var allIds = Database.Fetch<int>(sql).ToArray();
return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository);
}
}
}
}

View File

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

View File

@@ -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<PreValueCollection>(regex);
var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression<PreValueCollection>(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

View File

@@ -103,12 +103,9 @@ namespace Umbraco.Core.Persistence.Repositories
return new List<string>();
}
/// <summary>
/// Returns the Top Level Parent Guid Id
/// </summary>
protected override Guid NodeObjectTypeId
{
get { return new Guid(Constants.Conventions.Localization.DictionaryItemRootId); }
get { throw new NotImplementedException(); }
}
#endregion

View File

@@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IContentRepository : IRepositoryVersionable<int, IContent>, IRecycleBinRepository<IContent>
public interface IContentRepository : IRepositoryVersionable<int, IContent>, IRecycleBinRepository<IContent>, IDeleteMediaFilesRepository
{
/// <summary>
/// Get the count of published items

View File

@@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IContentTypeRepository : IRepositoryQueryable<int, IContentType>
public interface IContentTypeRepository : IRepositoryQueryable<int, IContentType>, IReadRepository<Guid, IContentType>
{
/// <summary>
/// Gets all entities of the specified <see cref="PropertyType"/> query

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IDeleteMediaFilesRepository
{
/// <summary>
/// Called to remove all files associated with entities when an entity is permanently deleted
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
bool DeleteMediaFiles(IEnumerable<string> files);
}
}

View File

@@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IMediaRepository : IRepositoryVersionable<int, IMedia>, IRecycleBinRepository<IMedia>
public interface IMediaRepository : IRepositoryVersionable<int, IMedia>, IRecycleBinRepository<IMedia>, IDeleteMediaFilesRepository
{
/// <summary>

View File

@@ -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<int, IMediaType>
public interface IMediaTypeRepository : IRepositoryQueryable<int, IMediaType>, IReadRepository<Guid, IMediaType>
{
/// <summary>
/// Gets all entities of the specified <see cref="PropertyType"/> query

View File

@@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IMemberRepository : IRepositoryVersionable<int, IMember>
public interface IMemberRepository : IRepositoryVersionable<int, IMember>, IDeleteMediaFilesRepository
{
/// <summary>
/// Finds members in a given role

View File

@@ -1,8 +1,9 @@
using Umbraco.Core.Models;
using System;
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IMemberTypeRepository : IRepositoryQueryable<int, IMemberType>
public interface IMemberTypeRepository : IRepositoryQueryable<int, IMemberType>, IReadRepository<Guid, IMemberType>
{
}

View File

@@ -18,12 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
/// <returns></returns>
bool EmptyRecycleBin();
/// <summary>
/// Called to remove all files associated with entities when recycle bin is emptied
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
bool DeleteFiles(IEnumerable<string> files);
}
}

View File

@@ -15,23 +15,8 @@ namespace Umbraco.Core.Persistence.Repositories
}
/// <summary>
/// Defines the implementation of a Repository
/// </summary>
public interface IRepository<in TId, TEntity> : IRepository
{
/// <summary>
/// Adds or Updates an Entity
/// </summary>
/// <param name="entity"></param>
void AddOrUpdate(TEntity entity);
/// <summary>
/// Deletes an Entity
/// </summary>
/// <param name="entity"></param>
void Delete(TEntity entity);
public interface IReadRepository<in TId, out TEntity> : IRepository
{
/// <summary>
/// Gets an Entity by Id
/// </summary>
@@ -52,5 +37,25 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="id"></param>
/// <returns></returns>
bool Exists(TId id);
}
}
//TODO: This should be decoupled! Shouldn't inherit from the IReadRepository and should be named IWriteRepository
/// <summary>
/// Defines the implementation of a Repository
/// </summary>
public interface IRepository<in TId, TEntity> : IReadRepository<TId, TEntity>
{
/// <summary>
/// Adds or Updates an Entity
/// </summary>
/// <param name="entity"></param>
void AddOrUpdate(TEntity entity);
/// <summary>
/// Deletes an Entity
/// </summary>
/// <param name="entity"></param>
void Delete(TEntity entity);
}
}

View File

@@ -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
/// <summary>
/// Defines the implementation of a Repository, which allows queries against the <see cref="TEntity"/>
/// </summary>

View File

@@ -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<IMedia> _contentXmlRepository;
private readonly ContentPreviewRepository<IMedia> _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<IMedia>(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
_contentPreviewRepository = new ContentPreviewRepository<IMedia>(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<int,IMedia>
@@ -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<NodeDto>("WHERE id = @ParentId", new { ParentId = entity.ParentId });
int level = parent.Level + 1;
int sortOrder =
Database.ExecuteScalar<int>("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<int>(
"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;

View File

@@ -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<ContentTypeDto, NodeDto>(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<ContentType2ContentTypeDto>("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
/// <summary>
/// Gets all entities of the specified <see cref="PropertyType"/> query
/// </summary>
/// <param name="query"></param>
/// <returns>An enumerable list of <see cref="IContentType"/> objects</returns>
public IEnumerable<IMediaType> GetByQuery(IQuery<PropertyType> 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<IMediaType>();
}
#region Overrides of PetaPocoRepositoryBase<int,IMedia>
@@ -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<IMediaType> PerformGetAll(params Guid[] ids)
{
if (ids.Any())
{
return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this);
}
else
{
var sql = new Sql().Select("id").From<NodeDto>(SqlSyntax).Where<NodeDto>(dto => dto.NodeObjectType == NodeObjectTypeId);
var allIds = Database.Fetch<int>(sql).ToArray();
return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this);
}
}
}
}

View File

@@ -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<IMember> _contentXmlRepository;
private readonly ContentPreviewRepository<IMember> _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<MediaFileSystem>();
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<IMembershipUser>
@@ -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();
}
}
}

View File

@@ -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<NodeDto>(x => x.NodeId);
var dtos =
Database.Fetch<MemberTypeReadOnlyDto, PropertyTypeReadOnlyDto, PropertyTypeGroupReadOnlyDto, MemberTypeReadOnlyDto>(
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<IMemberType> 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<NodeDto>(x => x.NodeId, SqlSyntax);
var dtos =
Database.Fetch<MemberTypeReadOnlyDto, PropertyTypeReadOnlyDto, PropertyTypeGroupReadOnlyDto, MemberTypeReadOnlyDto>(
new PropertyTypePropertyGroupRelator().Map, sql);
return BuildFromDtos(dtos);
}
/// <summary>
/// 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.

View File

@@ -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<TId, TEntity> : VersionableRepositoryBase<TId, TEntity>, IRecycleBinRepository<TEntity>
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
}
}
/// <summary>
/// Deletes all files passed in.
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
public virtual bool DeleteFiles(IEnumerable<string> files)
{
//ensure duplicates are removed
files = files.Distinct();
var allsuccess = true;
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
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<RecycleBinRepository<TId, TEntity>>("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:

View File

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

View File

@@ -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<TId, TEntity> : PetaPocoRepositoryBase<TId, TEntity>
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<IEnumerable<DataTypePreValueDto>>(() =>
{
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;
}
}
/// <summary>
/// Deletes all media files passed in.
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
public virtual bool DeleteMediaFiles(IEnumerable<string> files)
{
//ensure duplicates are removed
files = files.Distinct();
var allsuccess = true;
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
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<VersionableRepositoryBase<TId, TEntity>>("An error occurred while deleting file attached to nodes: " + file, e);
allsuccess = false;
}
});
return allsuccess;
}
}
}

View File

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

View File

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

View File

@@ -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;
}
/// <summary>

View File

@@ -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<BackOfficeIdentityUser, int>
{
public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
AuthenticationType = Constants.Security.BackOfficeAuthenticationType;
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(BackOfficeIdentityUser user)
{
return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager);
}
public static BackOfficeSignInManager Create(IdentityFactoryOptions<BackOfficeSignInManager> options, IOwinContext context)
{
return new BackOfficeSignInManager(context.GetUserManager<BackOfficeUserManager>(), context.Authentication);
}
/// <summary>
/// Creates a user identity and then signs the identity using the AuthenticationManager
/// </summary>
/// <param name="user"></param>
/// <param name="isPersistent"></param>
/// <param name="rememberBrowser"></param>
/// <returns></returns>
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);
}
}
}
}

View File

@@ -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
{
/// <summary>
/// Default back office user manager
/// </summary>
@@ -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<BackOfficeIdentityUser, int>(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
/// <summary>
/// Developers will need to override this to support custom 2 factor auth
/// </summary>
public override bool SupportsUserTwoFactor
{
get { return false; }

View File

@@ -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<BackOfficeIdentityUser, int>,
IUserLoginStore<BackOfficeIdentityUser, int>,
IUserRoleStore<BackOfficeIdentityUser, int>,
IUserSecurityStampStore<BackOfficeIdentityUser, int>
IUserSecurityStampStore<BackOfficeIdentityUser, int>,
IUserLockoutStore<BackOfficeIdentityUser, int>,
IUserTwoFactorStore<BackOfficeIdentityUser, int>
//TODO: This would require additional columns/tables for now people will need to implement this on their own
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
//IUserTwoFactorStore<BackOfficeIdentityUser, int>,
//TODO: This will require additional columns/tables
//IUserLockoutStore<BackOfficeIdentityUser, int>
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
//IQueryableUserStore<BackOfficeIdentityUser, int>
{
@@ -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
/// </summary>
/// <param name="userId"/>
/// <returns/>
public Task<BackOfficeIdentityUser> FindByIdAsync(int userId)
public async Task<BackOfficeIdentityUser> 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<BackOfficeIdentityUser>(user)));
return await Task.FromResult(AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user)));
}
/// <summary>
@@ -184,7 +182,7 @@ namespace Umbraco.Core.Security
/// </summary>
/// <param name="userName"/>
/// <returns/>
public Task<BackOfficeIdentityUser> FindByNameAsync(string userName)
public async Task<BackOfficeIdentityUser> FindByNameAsync(string userName)
{
ThrowIfDisposed();
var user = _userService.GetByUsername(userName);
@@ -195,7 +193,7 @@ namespace Umbraco.Core.Security
var result = AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user));
return Task.FromResult(result);
return await Task.FromResult(result);
}
/// <summary>
@@ -506,6 +504,118 @@ namespace Umbraco.Core.Security
return user;
}
/// <summary>
/// Sets whether two factor authentication is enabled for the user
/// </summary>
/// <param name="user"/><param name="enabled"/>
/// <returns/>
public virtual Task SetTwoFactorEnabledAsync(BackOfficeIdentityUser user, bool enabled)
{
user.TwoFactorEnabled = false;
return Task.FromResult(0);
}
/// <summary>
/// Returns whether two factor authentication is enabled for the user
/// </summary>
/// <param name="user"/>
/// <returns/>
public virtual Task<bool> GetTwoFactorEnabledAsync(BackOfficeIdentityUser user)
{
return Task.FromResult(false);
}
#region IUserLockoutStore
/// <summary>
/// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out.
/// </summary>
/// <param name="user"/>
/// <returns/>
/// <remarks>
/// Currently we do not suport a timed lock out, when they are locked out, an admin will have to reset the status
/// </remarks>
public Task<DateTimeOffset> GetLockoutEndDateAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException("user");
return user.LockoutEndDateUtc.HasValue
? Task.FromResult(DateTimeOffset.MaxValue)
: Task.FromResult(DateTimeOffset.MinValue);
}
/// <summary>
/// Locks a user out until the specified end date (set to a past date, to unlock a user)
/// </summary>
/// <param name="user"/><param name="lockoutEnd"/>
/// <returns/>
public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset lockoutEnd)
{
if (user == null) throw new ArgumentNullException("user");
user.LockoutEndDateUtc = lockoutEnd.UtcDateTime;
return Task.FromResult(0);
}
/// <summary>
/// Used to record when an attempt to access the user has failed
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<int> IncrementAccessFailedCountAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException("user");
user.AccessFailedCount++;
return Task.FromResult(user.AccessFailedCount);
}
/// <summary>
/// Used to reset the access failed count, typically after the account is successfully accessed
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException("user");
user.AccessFailedCount = 0;
return Task.FromResult(0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<int> GetAccessFailedCountAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException("user");
return Task.FromResult(user.AccessFailedCount);
}
/// <summary>
/// Returns true
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<bool> GetLockoutEnabledAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException("user");
return Task.FromResult(user.LockoutEnabled);
}
/// <summary>
/// Doesn't actually perform any function, users can always be locked out
/// </summary>
/// <param name="user"/><param name="enabled"/>
/// <returns/>
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);
}
}
}

View File

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

View File

@@ -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("-+", "+");
}
}
}

View File

@@ -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<ContentXmlDto>("WHERE nodeId = @Id", new { Id = content.Id }) !=
null;

View File

@@ -185,6 +185,19 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Gets an <see cref="IContentType"/> object by its Key
/// </summary>
/// <param name="id">Alias of the <see cref="IContentType"/> to retrieve</param>
/// <returns><see cref="IContentType"/></returns>
public IContentType GetContentType(Guid id)
{
using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
{
return repository.Get(id);
}
}
/// <summary>
/// Gets a list of all available <see cref="IContentType"/> objects
/// </summary>
@@ -198,6 +211,19 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Gets a list of all available <see cref="IContentType"/> objects
/// </summary>
/// <param name="ids">Optional list of ids</param>
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
public IEnumerable<IContentType> GetAllContentTypes(IEnumerable<Guid> ids)
{
using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
{
return repository.GetAll(ids.ToArray());
}
}
/// <summary>
/// Gets a list of children for a <see cref="IContentType"/> object
/// </summary>
@@ -213,25 +239,22 @@ namespace Umbraco.Core.Services
}
}
///// <summary>
///// Returns the content type descendant Ids for the content type specified
///// </summary>
///// <param name="contentTypeId"></param>
///// <returns></returns>
//internal IEnumerable<int> GetDescendantContentTypeIds(int contentTypeId)
//{
// using (var uow = UowProvider.GetUnitOfWork())
// {
// //method to return the child content type ids for the id specified
// Func<int, int[]> getChildIds =
// parentId =>
// uow.Database.Fetch<ContentType2ContentTypeDto>("WHERE parentContentTypeId = @Id", new {Id = parentId})
// .Select(x => x.ChildId).ToArray();
// //recursively get all descendant ids
// return getChildIds(contentTypeId).FlattenList(getChildIds);
// }
//}
/// <summary>
/// Gets a list of children for a <see cref="IContentType"/> object
/// </summary>
/// <param name="id">Id of the Parent</param>
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
public IEnumerable<IContentType> GetContentTypeChildren(Guid id)
{
using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
{
var found = GetContentType(id);
if (found == null) return Enumerable.Empty<IContentType>();
var query = Query<IContentType>.Builder.Where(x => x.ParentId == found.Id);
var contentTypes = repository.GetByQuery(query);
return contentTypes;
}
}
/// <summary>
/// Checks whether an <see cref="IContentType"/> item has any children
@@ -248,6 +271,23 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Checks whether an <see cref="IContentType"/> item has any children
/// </summary>
/// <param name="id">Id of the <see cref="IContentType"/></param>
/// <returns>True if the content type has any children otherwise False</returns>
public bool HasChildren(Guid id)
{
using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork()))
{
var found = GetContentType(id);
if (found == null) return false;
var query = Query<IContentType>.Builder.Where(x => x.ParentId == found.Id);
int count = repository.Count(query);
return count > 0;
}
}
/// <summary>
/// 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
}
}
/// <summary>
/// Gets an <see cref="IMediaType"/> object by its Id
/// </summary>
/// <param name="id">Id of the <see cref="IMediaType"/> to retrieve</param>
/// <returns><see cref="IMediaType"/></returns>
public IMediaType GetMediaType(Guid id)
{
using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
{
return repository.Get(id);
}
}
/// <summary>
/// Gets a list of all available <see cref="IMediaType"/> objects
/// </summary>
@@ -531,6 +584,19 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Gets a list of all available <see cref="IMediaType"/> objects
/// </summary>
/// <param name="ids">Optional list of ids</param>
/// <returns>An Enumerable list of <see cref="IMediaType"/> objects</returns>
public IEnumerable<IMediaType> GetAllMediaTypes(IEnumerable<Guid> ids)
{
using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
{
return repository.GetAll(ids.ToArray());
}
}
/// <summary>
/// Gets a list of children for a <see cref="IMediaType"/> object
/// </summary>
@@ -546,6 +612,23 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Gets a list of children for a <see cref="IMediaType"/> object
/// </summary>
/// <param name="id">Id of the Parent</param>
/// <returns>An Enumerable list of <see cref="IMediaType"/> objects</returns>
public IEnumerable<IMediaType> GetMediaTypeChildren(Guid id)
{
using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
{
var found = GetMediaType(id);
if (found == null) return Enumerable.Empty<IMediaType>();
var query = Query<IMediaType>.Builder.Where(x => x.ParentId == found.Id);
var contentTypes = repository.GetByQuery(query);
return contentTypes;
}
}
/// <summary>
/// Checks whether an <see cref="IMediaType"/> item has any children
/// </summary>
@@ -561,6 +644,23 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Checks whether an <see cref="IMediaType"/> item has any children
/// </summary>
/// <param name="id">Id of the <see cref="IMediaType"/></param>
/// <returns>True if the media type has any children otherwise False</returns>
public bool MediaTypeHasChildren(Guid id)
{
using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork()))
{
var found = GetMediaType(id);
if (found == null) return false;
var query = Query<IMediaType>.Builder.Where(x => x.ParentId == found.Id);
int count = repository.Count(query);
return count > 0;
}
}
/// <summary>
/// Saves a single <see cref="IMediaType"/> object
/// </summary>

View File

@@ -68,6 +68,13 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IContentType"/></returns>
IContentType GetContentType(string alias);
/// <summary>
/// Gets an <see cref="IContentType"/> object by its Key
/// </summary>
/// <param name="id">Alias of the <see cref="IContentType"/> to retrieve</param>
/// <returns><see cref="IContentType"/></returns>
IContentType GetContentType(Guid id);
/// <summary>
/// Gets a list of all available <see cref="IContentType"/> objects
/// </summary>
@@ -75,6 +82,13 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
IEnumerable<IContentType> GetAllContentTypes(params int[] ids);
/// <summary>
/// Gets a list of all available <see cref="IContentType"/> objects
/// </summary>
/// <param name="ids">Optional list of ids</param>
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
IEnumerable<IContentType> GetAllContentTypes(IEnumerable<Guid> ids);
/// <summary>
/// Gets a list of children for a <see cref="IContentType"/> object
/// </summary>
@@ -82,6 +96,13 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
IEnumerable<IContentType> GetContentTypeChildren(int id);
/// <summary>
/// Gets a list of children for a <see cref="IContentType"/> object
/// </summary>
/// <param name="id">Id of the Parent</param>
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
IEnumerable<IContentType> GetContentTypeChildren(Guid id);
/// <summary>
/// Saves a single <see cref="IContentType"/> object
/// </summary>
@@ -126,6 +147,13 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IMediaType"/></returns>
IMediaType GetMediaType(string alias);
/// <summary>
/// Gets an <see cref="IMediaType"/> object by its Id
/// </summary>
/// <param name="id">Id of the <see cref="IMediaType"/> to retrieve</param>
/// <returns><see cref="IMediaType"/></returns>
IMediaType GetMediaType(Guid id);
/// <summary>
/// Gets a list of all available <see cref="IMediaType"/> objects
/// </summary>
@@ -133,6 +161,13 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IMediaType"/> objects</returns>
IEnumerable<IMediaType> GetAllMediaTypes(params int[] ids);
/// <summary>
/// Gets a list of all available <see cref="IMediaType"/> objects
/// </summary>
/// <param name="ids">Optional list of ids</param>
/// <returns>An Enumerable list of <see cref="IMediaType"/> objects</returns>
IEnumerable<IMediaType> GetAllMediaTypes(IEnumerable<Guid> ids);
/// <summary>
/// Gets a list of children for a <see cref="IMediaType"/> object
/// </summary>
@@ -140,6 +175,13 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IMediaType"/> objects</returns>
IEnumerable<IMediaType> GetMediaTypeChildren(int id);
/// <summary>
/// Gets a list of children for a <see cref="IMediaType"/> object
/// </summary>
/// <param name="id">Id of the Parent</param>
/// <returns>An Enumerable list of <see cref="IMediaType"/> objects</returns>
IEnumerable<IMediaType> GetMediaTypeChildren(Guid id);
/// <summary>
/// Saves a single <see cref="IMediaType"/> object
/// </summary>
@@ -189,11 +231,25 @@ namespace Umbraco.Core.Services
/// <returns>True if the content type has any children otherwise False</returns>
bool HasChildren(int id);
/// <summary>
/// Checks whether an <see cref="IContentType"/> item has any children
/// </summary>
/// <param name="id">Id of the <see cref="IContentType"/></param>
/// <returns>True if the content type has any children otherwise False</returns>
bool HasChildren(Guid id);
/// <summary>
/// Checks whether an <see cref="IMediaType"/> item has any children
/// </summary>
/// <param name="id">Id of the <see cref="IMediaType"/></param>
/// <returns>True if the media type has any children otherwise False</returns>
bool MediaTypeHasChildren(int id);
/// <summary>
/// Checks whether an <see cref="IMediaType"/> item has any children
/// </summary>
/// <param name="id">Id of the <see cref="IMediaType"/></param>
/// <returns>True if the media type has any children otherwise False</returns>
bool MediaTypeHasChildren(Guid id);
}
}

View File

@@ -18,7 +18,6 @@ namespace Umbraco.Core.Services
/// </summary>
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<IDictionaryItem>.Builder.Where(x => x.ParentId == RootParentId);
var query = Query<IDictionaryItem>.Builder.Where(x => x.ParentId == null);
var items = repository.GetByQuery(query);
return items;

View File

@@ -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<ContentXmlDto>("WHERE nodeId = @Id", new { Id = id }) != null;
// int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco));
//}

View File

@@ -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<IMember>(member, false), this);
var args = new DeleteEventArgs<IMember>(member, false);
Deleted.RaiseEvent(args, this);
//remove any flagged media files
repository.DeleteMediaFiles(args.MediaFilesToDelete);
}
}
/// <summary>
@@ -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
/// <summary>
@@ -1250,7 +1254,7 @@ namespace Umbraco.Core.Services
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IMemberService, SaveEventArgs<IMember>> Saving;
/// <summary>
/// Occurs after Create
/// </summary>

View File

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

View File

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

View File

@@ -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
}
/// <summary>
/// Gets the local server unique identity.
/// Gets the unique local identity of the executing AppDomain.
/// </summary>
/// <returns>The unique identity of the local server.</returns>
protected string GetLocalIdentity()
{
return JsonConvert.SerializeObject(new
{
machineName = NetworkHelper.MachineName,
appDomainAppId = HttpRuntime.AppDomainAppId
});
}
/// <remarks>
/// <para>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.</para>
/// <para>Practically, all we really need is the guid, the other infos are here for information
/// and debugging purposes.</para>
/// </remarks>
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
/// <summary>
/// Gets the sync file path for the local server.

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Core.Sync
/// </summary>
public DatabaseServerMessengerOptions()
{
DaysToRetainInstructions = 100; // 100 days
DaysToRetainInstructions = 2; // 2 days
ThrottleSeconds = 5; // 5 seconds
}

View File

@@ -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
/// </summary>
internal static class ServerEnvironmentHelper
{
/// <summary>
/// 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.
/// </summary>
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco ) - or <c>null</c> if the url
/// cannot be determined at the moment (usually because the first request has not properly completed yet).</returns>
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<ApplicationContext>("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<ApplicationContext>("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<ApplicationContext>("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);
}
/// <summary>
@@ -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('/');
}
}
}

View File

@@ -58,11 +58,11 @@
</Reference>
<Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
<HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNet.Identity.Owin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll</HintPath>
<HintPath>..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -332,8 +332,12 @@
<Compile Include="HttpContextExtensions.cs" />
<Compile Include="IApplicationEventHandler.cs" />
<Compile Include="IDisposeOnRequestEnd.cs" />
<Compile Include="Logging\AsyncForwardingAppenderBase.cs" />
<Compile Include="Logging\LoggingEventContext.cs" />
<Compile Include="Logging\LoggingEventHelper.cs" />
<Compile Include="Logging\OwinLogger.cs" />
<Compile Include="Logging\OwinLoggerFactory.cs" />
<Compile Include="Logging\ParallelForwardingAppender.cs" />
<Compile Include="Manifest\GridEditorConverter.cs" />
<Compile Include="Models\AuditItem.cs" />
<Compile Include="Models\AuditType.cs" />
@@ -378,6 +382,7 @@
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddExternalLoginsTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddForeignKeysForLanguageAndDictionaryTables.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddMigrationTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddPublicAccessTables.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddUniqueIdPropertyTypeColumn.cs" />
@@ -386,6 +391,7 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\RemoveHelpTextColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\RemoveLanguageLocaleColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\RemoveStylesheetDataAndTables.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\RemoveUmbracoLoginsTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\UpdateUniqueIdToHaveCorrectIndexType.cs" />
<Compile Include="Media\Exif\IFD.cs" />
<Compile Include="Media\Exif\ImageFile.cs" />
@@ -416,6 +422,7 @@
<Compile Include="Persistence\Repositories\Interfaces\IMigrationEntryRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IPublicAccessRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IServerRegistrationRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IDeleteMediaFilesRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\ITaskRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\ITaskTypeRepository.cs" />
<Compile Include="Persistence\Repositories\MigrationEntryRepository.cs" />
@@ -425,14 +432,16 @@
<Compile Include="Persistence\Repositories\TaskTypeRepository.cs" />
<Compile Include="PropertyEditors\ValueConverters\GridValueConverter.cs" />
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
<Compile Include="Security\BackOfficeSignInManager.cs" />
<Compile Include="Security\BackOfficeUserManager.cs" />
<Compile Include="Security\BackOfficeUserStore.cs" />
<Compile Include="Security\MembershipPasswordHasher.cs" />
<Compile Include="SemVersionExtensions.cs" />
<Compile Include="ServiceProviderExtensions.cs" />
<Compile Include="IO\ResizedImage.cs" />
<Compile Include="IO\UmbracoMediaFile.cs" />
<Compile Include="Logging\DebugDiagnosticsLogger.cs" />
<Compile Include="Logging\AppDomainTokenFormatter.cs" />
<Compile Include="Logging\AppDomainTokenConverter.cs" />
<Compile Include="Logging\LoggerExtensions.cs" />
<Compile Include="Logging\LoggerResolver.cs" />
<Compile Include="Logging\ProfilingLogger.cs" />
@@ -1102,7 +1111,6 @@
<Compile Include="Models\Rdbms\User2NodeNotifyDto.cs" />
<Compile Include="Models\Rdbms\User2NodePermissionDto.cs" />
<Compile Include="Models\Rdbms\UserDto.cs" />
<Compile Include="Models\Rdbms\UserLoginDto.cs" />
<Compile Include="Models\Rdbms\UserTypeDto.cs" />
<Compile Include="NameValueCollectionExtensions.cs" />
<Compile Include="ObjectResolution\WeightedPluginAttribute.cs" />

View File

@@ -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
/// </summary>
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<UmbracoApplicationBase>("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;

View File

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

View File

@@ -3,8 +3,8 @@
<package id="AutoMapper" version="3.3.1" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.9" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Identity.Core" version="2.2.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Identity.Owin" version="2.2.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Identity.Owin" version="2.2.1" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Client" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.Bcl" version="1.1.9" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" />

View File

@@ -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<IMigrationEntryService>();
migrationEntryService.Setup(x => x.FindEntry(It.IsAny<string>(), It.IsAny<SemVersion>()))
@@ -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<IMigrationEntryService>();
migrationEntryService.Setup(x => x.FindEntry(It.IsAny<string>(), It.IsAny<SemVersion>()))

View File

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

View File

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

View File

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

View File

@@ -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<IAppender>();
asyncForwardingAppender.AddAppender(badAppender.Object);
badAppender
.Setup(ba => ba.DoAppend(It.IsAny<LoggingEvent>()))
.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<LoggingEvent>()), 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);
}
}
}

View File

@@ -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<string> ringBuffer = new RingBuffer<string>(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<string> ringBuffer = new RingBuffer<string>(1000);
Stopwatch ringWatch = new Stopwatch();
List<Task> ringTasks = new List<Task>();
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<string> ringBuffer = new RingBuffer<string>(1000);
Stopwatch ringWatch = new Stopwatch();
List<Task> ringTasks = new List<Task>();
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<object> queue = new RingBuffer<object>(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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<int>()))
.Returns(Mock.Of<IDataTypeDefinition>(
definition =>
definition.Id == 555
&& definition.PropertyEditorAlias == "myPropertyType"
&& definition.DatabaseType == DataTypeDatabaseType.Nvarchar));
var display = CreateCompositionContentTypeDisplay();
//Act
var result = Mapper.Map<IContentType>(display);
//Assert
//TODO: Now we need to assert all of the more complicated parts
Assert.AreEqual(display.Groups.Count(x => x.Inherited == false), result.PropertyGroups.Count);
}
[Test]
public void IContentType_To_ContentTypeDisplay()
{
@@ -227,17 +254,7 @@ namespace Umbraco.Tests.Models.Mapping
.Returns(new[] { new TextboxPropertyEditor() });
var contentType = MockedContentTypes.CreateTextpageContentType();
//ensure everything has ids
contentType.Id = 1234;
var itemid = 8888;
foreach (var propertyGroup in contentType.CompositionPropertyGroups)
{
propertyGroup.Id = itemid++;
}
foreach (var propertyType in contentType.CompositionPropertyTypes)
{
propertyType.Id = itemid++;
}
MockedContentTypes.EnsureAllIds(contentType, 8888);
//Act
@@ -289,6 +306,103 @@ namespace Umbraco.Tests.Models.Mapping
}
[Test]
public void IContentTypeComposition_To_ContentTypeDisplay()
{
//Arrange
// setup the mocks to return the data we want to test against...
// for any call to GetPreValuesCollectionByDataTypeId just return an empty dictionary for now
// TODO: but we'll need to change this to return some pre-values to test the mappings
_dataTypeService.Setup(x => x.GetPreValuesCollectionByDataTypeId(It.IsAny<int>()))
.Returns(new PreValueCollection(new Dictionary<string, PreValue>()));
//return a textbox property editor for any requested editor by alias
_propertyEditorResolver.Setup(resolver => resolver.GetByAlias(It.IsAny<string>()))
.Returns(new TextboxPropertyEditor());
//for testing, just return a list of whatever property editors we want
_propertyEditorResolver.Setup(resolver => resolver.PropertyEditors)
.Returns(new[] { new TextboxPropertyEditor() });
var ctMain = MockedContentTypes.CreateSimpleContentType();
//not assigned to tab
ctMain.AddPropertyType(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext)
{
Alias = "umbracoUrlName", Name = "Slug", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88
});
MockedContentTypes.EnsureAllIds(ctMain, 8888);
var ctChild1 = MockedContentTypes.CreateSimpleContentType("child1", "Child 1", ctMain, true);
ctChild1.AddPropertyType(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext)
{
Alias = "someProperty",
Name = "Some Property",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeDefinitionId = -88
}, "Another tab");
MockedContentTypes.EnsureAllIds(ctChild1, 7777);
var contentType = MockedContentTypes.CreateSimpleContentType("child2", "Child 2", ctChild1, true, "CustomGroup");
//not assigned to tab
contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext)
{
Alias = "umbracoUrlAlias", Name = "AltUrl", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88
});
MockedContentTypes.EnsureAllIds(contentType, 6666);
//Act
var result = Mapper.Map<ContentTypeDisplay>(contentType);
//Assert
Assert.AreEqual(contentType.Alias, result.Alias);
Assert.AreEqual(contentType.Description, result.Description);
Assert.AreEqual(contentType.Icon, result.Icon);
Assert.AreEqual(contentType.Id, result.Id);
Assert.AreEqual(contentType.Name, result.Name);
Assert.AreEqual(contentType.ParentId, result.ParentId);
Assert.AreEqual(contentType.Path, result.Path);
Assert.AreEqual(contentType.Thumbnail, result.Thumbnail);
Assert.AreEqual(contentType.IsContainer, result.IsContainer);
Assert.AreEqual(contentType.CreateDate, result.CreateDate);
Assert.AreEqual(contentType.UpdateDate, result.UpdateDate);
Assert.AreEqual(contentType.DefaultTemplate.Alias, result.DefaultTemplate.Alias);
//TODO: Now we need to assert all of the more complicated parts
Assert.AreEqual(contentType.CompositionPropertyGroups.Select(x => x.Name).Distinct().Count(), result.Groups.Count(x => x.Id != -666));
Assert.AreEqual(1, result.Groups.Count(x => x.Id == -666));
Assert.AreEqual(contentType.PropertyGroups.Count(), result.Groups.Count(x => x.Inherited == false && x.Id != -666));
var allPropertiesMapped = result.Groups.SelectMany(x => x.Properties).ToArray();
var allPropertyIdsMapped = allPropertiesMapped.Select(x => x.Id).ToArray();
var allSourcePropertyIds = contentType.CompositionPropertyTypes.Select(x => x.Id).ToArray();
Assert.AreEqual(contentType.PropertyTypes.Count(), allPropertiesMapped.Count(x => x.Inherited == false));
Assert.AreEqual(allPropertyIdsMapped.Count(), allSourcePropertyIds.Count());
Assert.IsTrue(allPropertyIdsMapped.ContainsAll(allSourcePropertyIds));
Assert.AreEqual(2, result.Groups.Count(x => x.ParentTabContentTypes.Any()));
Assert.IsTrue(result.Groups.SelectMany(x => x.ParentTabContentTypes).ContainsAll(new[] {ctMain.Id, ctChild1.Id}));
Assert.AreEqual(contentType.AllowedTemplates.Count(), result.AllowedTemplates.Count());
for (var i = 0; i < contentType.AllowedTemplates.Count(); i++)
{
Assert.AreEqual(contentType.AllowedTemplates.ElementAt(i).Id, result.AllowedTemplates.ElementAt(i).Id);
}
Assert.AreEqual(contentType.AllowedContentTypes.Count(), result.AllowedContentTypes.Count());
for (var i = 0; i < contentType.AllowedContentTypes.Count(); i++)
{
Assert.AreEqual(contentType.AllowedContentTypes.ElementAt(i).Id.Value, result.AllowedContentTypes.ElementAt(i));
}
}
private ContentTypeDisplay CreateSimpleContentTypeDisplay()
{
return new ContentTypeDisplay
@@ -359,6 +473,113 @@ namespace Umbraco.Tests.Models.Mapping
}
}
}
};
}
private ContentTypeDisplay CreateCompositionContentTypeDisplay()
{
return new ContentTypeDisplay
{
Alias = "test",
AllowAsRoot = true,
AllowedTemplates = new List<EntityBasic>
{
new EntityBasic
{
Id = 555,
Alias = "template1",
Name = "Template1"
},
new EntityBasic
{
Id = 556,
Alias = "template2",
Name = "Template2"
}
},
AllowedContentTypes = new[] { 666, 667 },
AvailableCompositeContentTypes = new List<EntityBasic>(),
DefaultTemplate = new EntityBasic() { Alias = "test" },
Description = "hello world",
Icon = "tree-icon",
Id = 1234,
Key = new Guid("8A60656B-3866-46AB-824A-48AE85083070"),
Name = "My content type",
Path = "-1,1234",
ParentId = -1,
Thumbnail = "tree-thumb",
IsContainer = true,
Groups = new List<PropertyGroupDisplay>()
{
new PropertyGroupDisplay
{
Id = 987,
Name = "Tab 1",
ParentGroupId = -1,
SortOrder = 0,
Inherited = false,
Properties = new List<PropertyTypeDisplay>
{
new PropertyTypeDisplay
{
Alias = "property1",
Description = "this is property 1",
Inherited = false,
Label = "Property 1",
Validation = new PropertyTypeValidation
{
Mandatory = false,
Pattern = ""
},
Editor = "myPropertyType",
Value = "value 1",
//View = ??? - isn't this the same as editor?
Config = new Dictionary<string, object>
{
{"item1", "value1"},
{"item2", "value2"}
},
SortOrder = 0,
DataTypeId = 555,
View = "blah"
}
}
},
new PropertyGroupDisplay
{
Id = 894,
Name = "Tab 2",
ParentGroupId = -1,
SortOrder = 0,
Inherited = true,
Properties = new List<PropertyTypeDisplay>
{
new PropertyTypeDisplay
{
Alias = "parentProperty",
Description = "this is a property from the parent",
Inherited = true,
Label = "Parent property",
Validation = new PropertyTypeValidation
{
Mandatory = false,
Pattern = ""
},
Editor = "myPropertyType",
Value = "parent value",
//View = ??? - isn't this the same as editor?
Config = new Dictionary<string, object>
{
{"item1", "value1"}
},
SortOrder = 0,
DataTypeId = 555,
View = "blah"
}
}
}
}
};
}

View File

@@ -258,6 +258,7 @@ namespace Umbraco.Tests.Persistence
using (Transaction transaction = Database.GetTransaction())
{
DatabaseSchemaHelper.CreateTable<DictionaryDto>();
DatabaseSchemaHelper.CreateTable<LanguageDto>();
DatabaseSchemaHelper.CreateTable<LanguageTextDto>();
//transaction.Complete();
@@ -577,18 +578,6 @@ namespace Umbraco.Tests.Persistence
}
}
[Test]
public void Can_Create_umbracoUserLogins_Table()
{
using (Transaction transaction = Database.GetTransaction())
{
DatabaseSchemaHelper.CreateTable<UserLoginDto>();
//transaction.Complete();
}
}
[Test]
public void Can_Create_umbracoUser_Table()
{

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