Merge branch 'dev-v7-contenttypeeditor' of https://github.com/umbraco/Umbraco-CMS into dev-v7-contenttypeeditor
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -129,3 +129,4 @@ src/*.boltdata/
|
||||
/src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.front.js
|
||||
src/umbraco.sln.ide/*
|
||||
build/UmbracoCms.*/
|
||||
src/.vs/
|
||||
|
||||
@@ -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 "(.+)?""
|
||||
ReplacementText="CurrentComment { get { return "$(BUILD_COMMENT)""/>
|
||||
|
||||
<FileUpdate Files="..\src\Umbraco.Core\Configuration\UmbracoVersion.cs"
|
||||
Condition="'$(BUILD_NIGHTLY)'!=''"
|
||||
Regex="CurrentComment { get { return "(.+)?""
|
||||
ReplacementText="CurrentComment { get { return "$(BUILD_NIGHTLY)""/>
|
||||
|
||||
<FileUpdate Files="..\src\Umbraco.Core\Configuration\UmbracoVersion.cs"
|
||||
Condition="'$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''"
|
||||
Regex="CurrentComment { get { return "(.+)?""
|
||||
ReplacementText="CurrentComment { get { return "$(BUILD_COMMENT)-$(BUILD_NIGHTLY)""/>
|
||||
|
||||
<!--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\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)")"/>
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_NIGHTLY)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_NIGHTLY)")"/>
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)")"/>
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'=='' AND '$(BUILD_NIGHTLY)'==''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)")"/>
|
||||
|
||||
<!--This updates the copyright year-->
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'==''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyCopyright\("Copyright © Umbraco (\d{4})"\)"
|
||||
ReplacementText="AssemblyCopyright("Copyright © Umbraco $([System.DateTime]::Now.ToString(`yyyy`))")"/>
|
||||
|
||||
@@ -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
|
||||
@@ -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")]
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
bool DisableFindContentByIdPath { get; }
|
||||
|
||||
string UrlProviderMode { get; }
|
||||
|
||||
string UmbracoApplicationUrl { get; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
105
src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs
Normal file
105
src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
17
src/Umbraco.Core/Logging/LoggingEventContext.cs
Normal file
17
src/Umbraco.Core/Logging/LoggingEventContext.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
31
src/Umbraco.Core/Logging/LoggingEventHelper.cs
Normal file
31
src/Umbraco.Core/Logging/LoggingEventHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
307
src/Umbraco.Core/Logging/ParallelForwardingAppender.cs
Normal file
307
src/Umbraco.Core/Logging/ParallelForwardingAppender.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)},
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
72
src/Umbraco.Core/Security/BackOfficeSignInManager.cs
Normal file
72
src/Umbraco.Core/Security/BackOfficeSignInManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
12
src/Umbraco.Core/SemVersionExtensions.cs
Normal file
12
src/Umbraco.Core/SemVersionExtensions.cs
Normal 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("-+", "+");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
//}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Sync
|
||||
/// </summary>
|
||||
public DatabaseServerMessengerOptions()
|
||||
{
|
||||
DaysToRetainInstructions = 100; // 100 days
|
||||
DaysToRetainInstructions = 2; // 2 days
|
||||
ThrottleSeconds = 5; // 5 seconds
|
||||
}
|
||||
|
||||
|
||||
@@ -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('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>()))
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
166
src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs
Normal file
166
src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Umbraco.Tests/Logging/DebugAppender.cs
Normal file
26
src/Umbraco.Tests/Logging/DebugAppender.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
326
src/Umbraco.Tests/Logging/ParallelForwarderTest.cs
Normal file
326
src/Umbraco.Tests/Logging/ParallelForwarderTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/Umbraco.Tests/Logging/RingBufferTest.cs
Normal file
147
src/Umbraco.Tests/Logging/RingBufferTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Binary file not shown.
@@ -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])
|
||||
;
|
||||
|
||||
@@ -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])
|
||||
;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user