diff --git a/.gitignore b/.gitignore index 113b207285..073a29d111 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ build/*.nupkg src/Umbraco.Tests/config/applications.config src/Umbraco.Tests/config/trees.config src/Umbraco.Web.UI/web.config +src/Umbraco.Web.UI/Config/ClientDependency.config *.orig src/Umbraco.Tests/config/404handlers.config src/Umbraco.Web.UI/[Vv]iews/*.cshtml @@ -142,3 +143,4 @@ apidocs/api/* build/docs.zip build/ui-docs.zip build/csharp-docs.zip +build/msbuild.log diff --git a/appveyor.yml b/appveyor.yml index b53eb55953..38495c0f0b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,7 +5,6 @@ build_script: cd build SET "release=" - FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i" SET nuGetFolder=C:\Users\appveyor\.nuget\packages @@ -13,34 +12,30 @@ build_script: ..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL ..\src\.nuget\NuGet.exe restore ..\src\Umbraco.Core\project.json -OutputDirectory %nuGetFolder% -Verbosity quiet - ..\src\.nuget\NuGet.exe restore ..\src\umbraco.datalayer\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet - ..\src\.nuget\NuGet.exe restore ..\src\Umbraco.Web\project.json -OutputDirectory %nuGetFolder% -Verbosity quiet - ..\src\.nuget\NuGet.exe restore ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet - - SET nuGetFolder=%CD%\..\src\packages\ - ..\src\.nuget\NuGet.exe restore ..\src\Umbraco.Tests\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet - ..\src\.nuget\NuGet.exe restore ..\src\umbraco.datalayer\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet - ..\src\.nuget\NuGet.exe restore ..\src\umbraco.controls\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet - - ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER% + ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER% + + SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% SET MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe" - %MSBUILD% "../src/Umbraco.Tests/Umbraco.Tests.csproj" /verbosity:minimal - - build.bat %release% build%APPVEYOR_BUILD_NUMBER% - + XCOPY "..\src\Umbraco.Tests\unit-test-log4net.CI.config" "..\src\Umbraco.Tests\unit-test-log4net.config" /Y + + %MSBUILD% "../src/Umbraco.Tests/Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly + + build.bat nopause %release% build%APPVEYOR_BUILD_NUMBER% + ECHO %PATH% test: assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll artifacts: - path: build\UmbracoCms.* +- path: build\msbuild.log notifications: - provider: Slack auth_token: diff --git a/build/Build.bat b/build/Build.bat index 995cef6a48..99f50b0a4c 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -11,19 +11,31 @@ FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SE FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED comment SET "comment=%%i" REM If there's arguments on the command line overrule UmbracoVersion.txt and use that as the version -IF [%1] NEQ [] (SET release=%1) -IF [%2] NEQ [] (SET comment=%2) ELSE (IF [%1] NEQ [] (SET "comment=")) +IF [%2] NEQ [] (SET release=%2) +IF [%3] NEQ [] (SET comment=%3) ELSE (IF [%2] NEQ [] (SET "comment=")) + +REM Get the "is continuous integration" from the parameters +SET "isci=0" +IF [%1] NEQ [] (SET isci=1) SET version=%release% IF [%comment%] EQU [] (SET version=%release%) ELSE (SET version=%release%-%comment%) + +ECHO. ECHO Building Umbraco %version% +ECHO. + +SET MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe" +SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %release% +ECHO. ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle RD ..\src\Umbraco.Web.UI.Client\build /Q /S RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S +ECHO. ECHO Removing existing built files to make sure everything is clean as a whistle RMDIR /Q /S _BuildOutput DEL /F /Q UmbracoCms.*.zip @@ -31,33 +43,66 @@ DEL /F /Q UmbracoExamine.*.zip DEL /F /Q UmbracoCms.*.nupkg DEL /F /Q webpihash.txt +ECHO. ECHO Making sure Git is in the path so that the build can succeed CALL InstallGit.cmd -ECHO Performing MSBuild and producing Umbraco binaries zip files +REM Adding the default Git path so that if it's installed it can actually be found +REM This is necessary because SETLOCAL is on in InstallGit.cmd so that one might find Git, +REM but the path setting is lost due to SETLOCAL +path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% + +ECHO. +ECHO Making sure we have a web.config +IF NOT EXIST %CD%\..\src\Umbraco.Web.UI\web.config COPY %CD%\..\src\Umbraco.Web.UI\web.Template.config %CD%\..\src\Umbraco.Web.UI\web.config + +ECHO. +ECHO Restoring NuGet packages SET nuGetFolder=%CD%\..\src\packages\ ..\src\.nuget\NuGet.exe restore ..\src\Umbraco.Core\project.json -OutputDirectory %nuGetFolder% -Verbosity quiet ..\src\.nuget\NuGet.exe restore ..\src\umbraco.datalayer\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet ..\src\.nuget\NuGet.exe restore ..\src\Umbraco.Web\project.json -OutputDirectory %nuGetFolder% -Verbosity quiet ..\src\.nuget\NuGet.exe restore ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet -"%ProgramFiles(x86)%"\MSBuild\14.0\Bin\MSBuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /verbosity:minimal +ECHO. +ECHO Performing MSBuild and producing Umbraco binaries zip files +ECHO This takes a few minutes and logging is set to report warnings +ECHO and errors only so it might seems like nothing is happening for a while. +ECHO You can check the msbuild.log file for progress. +ECHO. +%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /p:NugetPackagesDirectory=%nuGetFolder% /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /fileLogger +IF ERRORLEVEL 1 GOTO :error + +ECHO. ECHO Setting node_modules folder to hidden to prevent VS13 from crashing on it while loading the websites project attrib +h ..\src\Umbraco.Web.UI.Client\node_modules +ECHO. ECHO Adding Web.config transform files to the NuGet package REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform +ECHO. ECHO Packing the NuGet release files ..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% -Symbols -Verbosity quiet ..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %version% -Verbosity quiet - -IF ERRORLEVEL 1 GOTO :showerror +IF ERRORLEVEL 1 GOTO :error -ECHO No errors were detected but you still may see some in the output, then it's time to investigate. -ECHO You might see some warnings but that is completely normal. +:success +ECHO. +ECHO No errors were detected! +ECHO There may still be some in the output, which you would need to investigate. +ECHO Warnings are usually normal. +ECHO. +ECHO. GOTO :EOF -:showerror -PAUSE +:error + +ECHO. +ECHO Errors were detected! +ECHO. + +REM don't pause if continuous integration else the build server waits forever +REM before cancelling the build (and, there is noone to read the output anyways) +IF isci NEQ 1 PAUSE diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat index 6c11cc9fc5..8a07ee380a 100644 --- a/build/BuildBelle.bat +++ b/build/BuildBelle.bat @@ -23,6 +23,7 @@ ECHO Change directory to %CD%\..\src\Umbraco.Web.UI.Client\ CD %CD%\..\src\Umbraco.Web.UI.Client\ ECHO Do npm install and the grunt build of Belle +call npm cache clean --quiet call npm install --quiet call npm install -g grunt-cli --quiet call npm install -g bower --quiet diff --git a/build/InstallGit.cmd b/build/InstallGit.cmd index b6ba71df9b..26f8088f35 100644 --- a/build/InstallGit.cmd +++ b/build/InstallGit.cmd @@ -2,13 +2,13 @@ SETLOCAL REM SETLOCAL is on, so changes to the path not persist to the actual user's path -git.exe 2> NUL +git.exe --version if %ERRORLEVEL%==9009 GOTO :trydefaultpath GOTO :EOF :trydefaultpath path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% -git.exe 2> NUL +git.exe --version if %ERRORLEVEL%==9009 GOTO :showerror GOTO :EOF diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 5a57989579..6177065595 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -35,9 +35,9 @@ - - - + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 66e82ac488..5ee38e57fe 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -32,6 +32,7 @@ + diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 5ebaa6d02a..0e62fb0749 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -97,7 +97,12 @@ if ($project) { $umbracoUIXMLSource = Join-Path $installPath "UmbracoFiles\Umbraco\Config\Create\UI.xml" $umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml" Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force - } + } else { + $upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*" + $upgradeView = Join-Path $umbracoFolder "Views\install\" + Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}" + Copy-Item $upgradeViewSource $upgradeView -Force + } $installFolder = Join-Path $projectPath "Install" if(Test-Path $installFolder) { diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 803b95f9af..71deceddf9 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 8.0.0 -beta \ No newline at end of file +alpha0000 \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCEHelper.cs b/src/SQLCE4Umbraco/SqlCEHelper.cs index 26a781360e..ab6f686c21 100644 --- a/src/SQLCE4Umbraco/SqlCEHelper.cs +++ b/src/SQLCE4Umbraco/SqlCEHelper.cs @@ -40,15 +40,10 @@ namespace SqlCE4Umbraco var localConnection = new SqlCeConnection(ConnectionString); if (!System.IO.File.Exists(ReplaceDataDirectory(localConnection.Database))) { - var sqlCeEngine = new SqlCeEngine(ConnectionString); - sqlCeEngine.CreateDatabase(); - - // SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here - // since it's been like this for quite some time - //using (var sqlCeEngine = new SqlCeEngine(ConnectionString)) - //{ - // sqlCeEngine.CreateDatabase(); - //} + using (var sqlCeEngine = new SqlCeEngine(ConnectionString)) + { + sqlCeEngine.CreateDatabase(); + } } } diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 0f61a177ce..259005d5b1 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("8.0.0")] -[assembly: AssemblyInformationalVersion("8.0.0-beta")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("8.0.0-alpha0000")] \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index f4e449499b..6b797307ac 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -5,38 +5,31 @@ using System.Web.Caching; namespace Umbraco.Core.Cache { - internal class NullCacheProvider : IRuntimeCacheProvider + /// + /// Represents a cache provider that does not cache anything. + /// + public class NullCacheProvider : IRuntimeCacheProvider { public virtual void ClearAllCache() - { - } + { } public virtual void ClearCacheItem(string key) - { - } + { } public virtual void ClearCacheObjectTypes(string typeName) - { - } + { } public virtual void ClearCacheObjectTypes() - { - } + { } public virtual void ClearCacheObjectTypes(Func predicate) - { - } - - - + { } public virtual void ClearCacheByKeySearch(string keyStartsWith) - { - } + { } public virtual void ClearCacheByKeyExpression(string regexString) - { - } + { } public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { @@ -64,8 +57,6 @@ namespace Umbraco.Core.Cache } public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - - } + { } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index 811a6e5310..226cf35933 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -1,24 +1,24 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Reflection; using System.Runtime.Caching; using System.Text.RegularExpressions; using System.Threading; using System.Web.Caching; -using Umbraco.Core.Logging; using Umbraco.Core.Plugins; using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache { /// + /// Represents a cache provider that caches item in a . /// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache /// - internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider + /// The is created with name "in-memory". That name is + /// used to retrieve configuration options. It does not identify the memory cache, i.e. + /// each instance of this class has its own, independent, memory cache. + public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); internal ObjectCache MemoryCache; diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index 9c448efa6a..c7fd00d39a 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -3,14 +3,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using System.Web.Caching; namespace Umbraco.Core.Cache { /// - /// A cache provider that statically caches everything in an in memory dictionary + /// Represents a cache provider that statically caches item in a concurrent dictionary. /// - internal class StaticCacheProvider : ICacheProvider + public class StaticCacheProvider : ICacheProvider { internal readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary(); @@ -75,7 +74,6 @@ namespace Umbraco.Core.Cache public virtual object GetCacheItem(string cacheKey, Func getCacheItem) { return StaticCache.GetOrAdd(cacheKey, key => getCacheItem()); - } - + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index f24fb61610..aa3725b819 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -73,6 +73,11 @@ /// public const string DataTypes = "dataTypes"; + /// + /// alias for the packages tree + /// + public const string Packages = "packager"; + /// /// alias for the dictionary tree. /// @@ -118,6 +123,11 @@ /// public const string UserTypes = "userTypes"; + /// + /// alias for the user permissions tree. + /// + public const string UserPermissions = "userPermissions"; + /// /// alias for the users tree. /// diff --git a/src/Umbraco.Core/DecimalExtensions.cs b/src/Umbraco.Core/DecimalExtensions.cs new file mode 100644 index 0000000000..b4d74fdb2e --- /dev/null +++ b/src/Umbraco.Core/DecimalExtensions.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Core +{ + /// + /// Provides extension methods for System.Decimal. + /// + /// See System.Decimal on MSDN and also + /// http://stackoverflow.com/questions/4298719/parse-decimal-and-filter-extra-0-on-the-right/4298787#4298787. + /// + public static class DecimalExtensions + { + /// + /// Gets the normalized value. + /// + /// The value to normalize. + /// The normalized value. + /// Normalizing changes the scaling factor and removes trailing zeroes, + /// so 1.2500m comes out as 1.25m. + public static decimal Normalize(this decimal value) + { + return value / 1.000000000000000000000000000000000m; + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs index 59edb78be7..4d185c4c41 100644 --- a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs @@ -46,6 +46,7 @@ namespace Umbraco.Core.DependencyInjection container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); + container.RegisterSingleton(); container.Register(factory => { var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 38bc358e94..ec055f21e2 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -69,7 +69,16 @@ namespace Umbraco.Core.IO } } - public static string AppPlugins + public static string AppCode + { + get + { + //NOTE: this is not configurable and shouldn't need to be + return "~/App_Code"; + } + } + + public static string AppPlugins { get { diff --git a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs index 74a1de81f4..fcb0f183ec 100644 --- a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs +++ b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs @@ -1,20 +1,21 @@ -using System; using log4net.Appender; using log4net.Core; using log4net.Util; +using System; +using System.Runtime.Remoting.Messaging; namespace Umbraco.Core.Logging { - /// - /// Based on https://github.com/cjbhaines/Log4Net.Async - /// + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// public abstract class AsyncForwardingAppenderBase : ForwardingAppender { #region Private Members private const FixFlags DefaultFixFlags = FixFlags.Partial; - private FixFlags _fixFlags = DefaultFixFlags; - private LoggingEventHelper _loggingEventHelper; + private FixFlags fixFlags = DefaultFixFlags; + private LoggingEventHelper loggingEventHelper; #endregion Private Members @@ -22,10 +23,25 @@ namespace Umbraco.Core.Logging public FixFlags Fix { - get { return _fixFlags; } + get { return fixFlags; } set { SetFixFlags(value); } } + /// + /// Returns HttpContext.Current + /// + protected internal object HttpContext + { + get + { + return CallContext.HostContext; + } + set + { + CallContext.HostContext = value; + } + } + /// /// The logger name that will be used for logging internal errors. /// @@ -38,7 +54,7 @@ namespace Umbraco.Core.Logging public override void ActivateOptions() { base.ActivateOptions(); - _loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags); + loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags); InitializeAppenders(); } @@ -52,10 +68,10 @@ namespace Umbraco.Core.Logging private void SetFixFlags(FixFlags newFixFlags) { - if (newFixFlags != _fixFlags) + if (newFixFlags != fixFlags) { - _loggingEventHelper.Fix = newFixFlags; - _fixFlags = newFixFlags; + loggingEventHelper.Fix = newFixFlags; + fixFlags = newFixFlags; InitializeAppenders(); } } @@ -84,7 +100,7 @@ namespace Umbraco.Core.Logging protected void ForwardInternalError(string message, Exception exception, Type thisType) { LogLog.Error(thisType, message, exception); - var loggingEvent = _loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception); + var loggingEvent = loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception); ForwardLoggingEvent(loggingEvent, thisType); } diff --git a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs index cb58ebbfaa..c226bd03c8 100644 --- a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs +++ b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs @@ -1,6 +1,7 @@ using log4net.Core; using log4net.Util; using System; +using System.ComponentModel; using System.Runtime.Remoting.Messaging; using System.Security.Principal; using System.Threading; @@ -12,7 +13,11 @@ namespace Umbraco.Core.Logging /// /// 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/ + /// This is an old/deprecated logger and has been superceded by ParallelForwardingAppender which is included in Umbraco and + /// also by AsyncForwardingAppender in the Log4Net.Async library. /// + [Obsolete("This is superceded by the ParallelForwardingAppender, this will be removed in v8")] + [EditorBrowsable(EditorBrowsableState.Never)] public class AsynchronousRollingFileAppender : RollingFileAppender { private RingBuffer pendingAppends; @@ -198,79 +203,4 @@ namespace Umbraco.Core.Logging } } - internal interface IQueue - { - void Enqueue(T item); - bool TryDequeue(out T ret); - } - - internal class RingBuffer : IQueue - { - private readonly object lockObject = new object(); - private readonly T[] buffer; - private readonly int size; - private int readIndex = 0; - private int writeIndex = 0; - private bool bufferFull = false; - - public int Size { get { return size; } } - - public event Action BufferOverflow; - - 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; - } - } - - 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; - } - - ret = buffer[readIndex]; - buffer[readIndex] = default(T); - readIndex = (++readIndex) % size; - bufferFull = false; - return true; - } - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/IQueue.cs b/src/Umbraco.Core/Logging/IQueue.cs new file mode 100644 index 0000000000..d063993ef6 --- /dev/null +++ b/src/Umbraco.Core/Logging/IQueue.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Logging +{ + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// + /// + internal interface IQueue + { + void Enqueue(T item); + bool TryDequeue(out T ret); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs new file mode 100644 index 0000000000..5bcf119b0d --- /dev/null +++ b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs @@ -0,0 +1,46 @@ +namespace Umbraco.Core.Logging +{ + using System; + using System.Runtime.CompilerServices; + + using ImageProcessor.Common.Exceptions; + + /// + /// A logger for explicitly logging ImageProcessor exceptions. + /// + /// Creating this logger is enough for ImageProcessor to find and replace its in-built debug logger + /// without any additional configuration required. This class currently has to be public in order + /// to do so. + /// + /// + public sealed class ImageProcessorLogger : ImageProcessor.Common.Exceptions.ILogger + { + /// + /// Logs the specified message as an error. + /// + /// The type calling the logger. + /// The message to log. + /// The property or method name calling the log. + /// The line number where the method is called. + public void Log(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) + { + // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. + var message = $"{callerName} {lineNumber} : {text}"; + LogHelper.Error(string.Empty, new ImageProcessingException(message)); + } + + /// + /// Logs the specified message as an error. + /// + /// The type calling the logger. + /// The message to log. + /// The property or method name calling the log. + /// The line number where the method is called. + public void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) + { + // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. + var message = $"{callerName} {lineNumber} : {text}"; + LogHelper.Error(type, string.Empty, new ImageProcessingException(message)); + } + } +} diff --git a/src/Umbraco.Core/Logging/LoggingEventContext.cs b/src/Umbraco.Core/Logging/LoggingEventContext.cs index 159af4266b..88222e3c05 100644 --- a/src/Umbraco.Core/Logging/LoggingEventContext.cs +++ b/src/Umbraco.Core/Logging/LoggingEventContext.cs @@ -3,15 +3,18 @@ using log4net.Core; namespace Umbraco.Core.Logging { /// - /// Based on https://github.com/cjbhaines/Log4Net.Async + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 /// - internal class LoggingEventContext + internal sealed class LoggingEventContext { - public LoggingEventContext(LoggingEvent loggingEvent) + public LoggingEventContext(LoggingEvent loggingEvent, object httpContext) { LoggingEvent = loggingEvent; + HttpContext = httpContext; } public LoggingEvent LoggingEvent { get; set; } + + public object HttpContext { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/LoggingEventHelper.cs b/src/Umbraco.Core/Logging/LoggingEventHelper.cs index c788e115f2..129098279a 100644 --- a/src/Umbraco.Core/Logging/LoggingEventHelper.cs +++ b/src/Umbraco.Core/Logging/LoggingEventHelper.cs @@ -4,9 +4,9 @@ using log4net.Core; namespace Umbraco.Core.Logging { /// - /// Based on https://github.com/cjbhaines/Log4Net.Async + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 /// - internal class LoggingEventHelper + internal sealed class LoggingEventHelper { // needs to be a seperate class so that location is determined correctly by log4net when required @@ -23,8 +23,10 @@ namespace Umbraco.Core.Logging public LoggingEvent CreateLoggingEvent(Level level, string message, Exception exception) { - var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception); - loggingEvent.Fix = Fix; + var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception) + { + Fix = Fix + }; return loggingEvent; } } diff --git a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs index cf7efdb4c4..48bb3ec710 100644 --- a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs +++ b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs @@ -1,9 +1,9 @@ +using log4net.Core; +using log4net.Util; using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; -using log4net.Core; -using log4net.Util; namespace Umbraco.Core.Logging { @@ -11,7 +11,7 @@ namespace Umbraco.Core.Logging /// An asynchronous appender based on /// /// - /// Based on https://github.com/cjbhaines/Log4Net.Async + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 /// public class ParallelForwardingAppender : AsyncForwardingAppenderBase, IDisposable { @@ -22,11 +22,11 @@ namespace Umbraco.Core.Logging private CancellationTokenSource _loggingCancelationTokenSource; private CancellationToken _loggingCancelationToken; private Task _loggingTask; - private Double _shutdownFlushTimeout = 1; - private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(1); + private Double _shutdownFlushTimeout = 2; + private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(2); private static readonly Type ThisType = typeof(ParallelForwardingAppender); - private volatile bool _shutDownRequested; - private int? _bufferSize = DefaultBufferSize; + private volatile bool shutDownRequested; + private int? bufferSize = DefaultBufferSize; #endregion Private Members @@ -37,8 +37,8 @@ namespace Umbraco.Core.Logging /// public override int? BufferSize { - get { return _bufferSize; } - set { _bufferSize = value; } + get { return bufferSize; } + set { bufferSize = value; } } public int BufferEntryCount @@ -67,7 +67,12 @@ namespace Umbraco.Core.Logging protected override string InternalLoggerName { - get { return "ParallelForwardingAppender"; } + get + { + { + return "ParallelForwardingAppender"; + } + } } #endregion Properties @@ -83,7 +88,7 @@ namespace Umbraco.Core.Logging private void StartForwarding() { - if (_shutDownRequested) + if (shutDownRequested) { return; } @@ -111,7 +116,7 @@ namespace Umbraco.Core.Logging private void CompleteSubscriberTask() { - _shutDownRequested = true; + shutDownRequested = true; if (_loggingEvents == null || _loggingEvents.IsAddingCompleted) { return; @@ -154,7 +159,7 @@ namespace Umbraco.Core.Logging 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); + _loggingEvents.Add(new LoggingEventContext(loggingEvent, HttpContext), _loggingCancelationToken); } protected override void Append(LoggingEvent[] loggingEvents) @@ -187,6 +192,7 @@ namespace Umbraco.Core.Logging //This call blocks until an item is available or until adding is completed foreach (var entry in _loggingEvents.GetConsumingEnumerable(_loggingCancelationToken)) { + HttpContext = entry.HttpContext; ForwardLoggingEvent(entry.LoggingEvent, ThisType); } } diff --git a/src/Umbraco.Core/Logging/RingBuffer.cs b/src/Umbraco.Core/Logging/RingBuffer.cs new file mode 100644 index 0000000000..8dd78129b8 --- /dev/null +++ b/src/Umbraco.Core/Logging/RingBuffer.cs @@ -0,0 +1,78 @@ +using System; + +namespace Umbraco.Core.Logging +{ + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// + /// + internal sealed class RingBuffer : IQueue + { + private readonly object lockObject = new object(); + private readonly T[] buffer; + private readonly int size; + private int readIndex = 0; + private int writeIndex = 0; + private bool bufferFull = false; + + public int Size { get { return size; } } + + public event Action BufferOverflow; + + 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; + } + } + + 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; + } + + ret = buffer[readIndex]; + buffer[readIndex] = default(T); + readIndex = (++readIndex) % size; + bufferFull = false; + return true; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index be24049364..b2d816e0de 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -77,14 +78,19 @@ namespace Umbraco.Core.Models PublishedState = PublishedState.Unpublished; } - private static readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); - private static readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); - private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - private static readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); - private static readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); - private static readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); - private static readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeName); - private static readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo(x => x.PermissionsChanged); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); + public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); + public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); + public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); + public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeName); + public readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo(x => x.PermissionsChanged); + } /// /// Gets or sets the template used by the Content. @@ -97,21 +103,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual ITemplate Template { - get - { - if (_template == null) - return _contentType.DefaultTemplate; - - return _template; - } - set - { - SetPropertyValueAndDetectChanges(o => - { - _template = value; - return _template; - }, _template, TemplateSelector); - } + get { return _template ?? _contentType.DefaultTemplate; } + set { SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); } } /// @@ -151,13 +144,9 @@ namespace Umbraco.Core.Models get { return _published; } internal set { - SetPropertyValueAndDetectChanges(o => - { - _published = value; - _publishedOriginal = _publishedOriginal ?? _published; - PublishedState = _published ? PublishedState.Published : PublishedState.Unpublished; - return _published; - }, _published, PublishedSelector); + SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); + _publishedOriginal = _publishedOriginal ?? _published; + PublishedState = _published ? PublishedState.Published : PublishedState.Unpublished; } } @@ -171,17 +160,11 @@ namespace Umbraco.Core.Models /// Language of the data contained within this Content object. /// [Obsolete("This is not used and will be removed from the codebase in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public string Language { get { return _language; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _language = value; - return _language; - }, _language, LanguageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } } /// @@ -191,14 +174,7 @@ namespace Umbraco.Core.Models public DateTime? ReleaseDate { get { return _releaseDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _releaseDate = value; - return _releaseDate; - }, _releaseDate, ReleaseDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); } } /// @@ -208,14 +184,7 @@ namespace Umbraco.Core.Models public DateTime? ExpireDate { get { return _expireDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _expireDate = value; - return _expireDate; - }, _expireDate, ExpireDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); } } /// @@ -225,14 +194,7 @@ namespace Umbraco.Core.Models public virtual int WriterId { get { return _writer; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _writer = value; - return _writer; - }, _writer, WriterSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _writer, Ps.Value.WriterSelector); } } /// @@ -245,14 +207,7 @@ namespace Umbraco.Core.Models internal string NodeName { get { return _nodeName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _nodeName = value; - return _nodeName; - }, _nodeName, NodeNameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); } } /// @@ -262,14 +217,7 @@ namespace Umbraco.Core.Models internal bool PermissionsChanged { get { return _permissionsChanged; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _permissionsChanged = value; - return _permissionsChanged; - }, _permissionsChanged, PermissionsChangedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _permissionsChanged, Ps.Value.PermissionsChangedSelector); } } /// diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 16fd59b456..9de5afddb4 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -82,19 +82,24 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); - private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); + public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + } protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyCollectionSelector); } /// @@ -115,18 +120,18 @@ namespace Umbraco.Core.Models set { _parentId = new Lazy(() => value); - OnPropertyChanged(ParentIdSelector); + OnPropertyChanged(Ps.Value.ParentIdSelector); } } /// /// Sets the ParentId from the lazy integer id /// - /// Id of the Parent + /// Id of the Parent internal protected void SetLazyParentId(Lazy parentId) { _parentId = parentId; - OnPropertyChanged(ParentIdSelector); + OnPropertyChanged(Ps.Value.ParentIdSelector); } /// @@ -136,14 +141,7 @@ namespace Umbraco.Core.Models public virtual string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -153,14 +151,7 @@ namespace Umbraco.Core.Models public virtual int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -170,14 +161,7 @@ namespace Umbraco.Core.Models public virtual int Level { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } /// @@ -187,14 +171,7 @@ namespace Umbraco.Core.Models public virtual string Path //Setting this value should be handled by the class not the user { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } /// @@ -204,14 +181,7 @@ namespace Umbraco.Core.Models public virtual int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } /// @@ -223,14 +193,7 @@ namespace Umbraco.Core.Models public virtual bool Trashed //Setting this value should be handled by the class not the user { get { return _trashed; } - internal set - { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); - } + internal set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } /// @@ -255,14 +218,7 @@ namespace Umbraco.Core.Models } return _contentTypeId; } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeId = value; - return _contentTypeId; - }, _contentTypeId, DefaultContentTypeIdSelector); - } + protected set { SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); } } /// diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index d711cf5a1a..8590fc66d4 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -48,8 +48,13 @@ namespace Umbraco.Core.Models _allowedTemplates = new List(); } - private static readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); - private static readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); + public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + } /// /// Gets or sets the alias of the default Template. @@ -70,14 +75,7 @@ namespace Umbraco.Core.Models internal int DefaultTemplateId { get { return _defaultTemplate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _defaultTemplate = value; - return _defaultTemplate; - }, _defaultTemplate, DefaultTemplateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); } } /// @@ -92,11 +90,7 @@ namespace Umbraco.Core.Models get { return _allowedTemplates; } set { - SetPropertyValueAndDetectChanges(o => - { - _allowedTemplates = value; - return _allowedTemplates; - }, _allowedTemplates, AllowedTemplatesSelector, + SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 21ff40ce05..a0b9bfa8f1 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -67,33 +67,38 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); - private static readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); - private static readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); - private static readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); - private static readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); - private static readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); - private static readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); + public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); + public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); + public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); + public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); + public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); + public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + } protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyGroupCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); } protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyTypeCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); } /// @@ -115,7 +120,7 @@ namespace Umbraco.Core.Models set { _parentId = new Lazy(() => value); - OnPropertyChanged(ParentIdSelector); + OnPropertyChanged(Ps.Value.ParentIdSelector); } } @@ -126,14 +131,7 @@ namespace Umbraco.Core.Models public virtual string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -143,14 +141,7 @@ namespace Umbraco.Core.Models public virtual int Level //NOTE Is this relevant for a ContentType? { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } /// @@ -160,14 +151,7 @@ namespace Umbraco.Core.Models public virtual string Path //NOTE Is this relevant for a ContentType? { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } /// @@ -179,12 +163,10 @@ namespace Umbraco.Core.Models get { return _alias; } set { - SetPropertyValueAndDetectChanges(o => - { - //_alias = value.ToSafeAlias(); - _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); - return _alias; - }, _alias, AliasSelector); + SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); } } @@ -195,14 +177,7 @@ namespace Umbraco.Core.Models public virtual string Description { get { return _description; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _description = value; - return _description; - }, _description, DescriptionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); } } /// @@ -212,14 +187,7 @@ namespace Umbraco.Core.Models public virtual int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -229,14 +197,7 @@ namespace Umbraco.Core.Models public virtual string Icon { get { return _icon; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _icon = value; - return _icon; - }, _icon, IconSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); } } /// @@ -246,14 +207,7 @@ namespace Umbraco.Core.Models public virtual string Thumbnail { get { return _thumbnail; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _thumbnail = value; - return _thumbnail; - }, _thumbnail, ThumbnailSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); } } /// @@ -263,14 +217,7 @@ namespace Umbraco.Core.Models public virtual int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } /// @@ -280,14 +227,7 @@ namespace Umbraco.Core.Models public virtual bool AllowedAsRoot { get { return _allowedAsRoot; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _allowedAsRoot = value; - return _allowedAsRoot; - }, _allowedAsRoot, AllowedAsRootSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); } } /// @@ -300,14 +240,7 @@ namespace Umbraco.Core.Models public virtual bool IsContainer { get { return _isContainer; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isContainer = value; - return _isContainer; - }, _isContainer, IsContainerSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); } } /// @@ -318,14 +251,7 @@ namespace Umbraco.Core.Models public virtual bool Trashed //NOTE Is this relevant for a ContentType? { get { return _trashed; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } private IDictionary _additionalData; @@ -347,15 +273,11 @@ namespace Umbraco.Core.Models get { return _allowedContentTypes; } set { - SetPropertyValueAndDetectChanges(o => - { - _allowedContentTypes = value; - return _allowedContentTypes; - }, _allowedContentTypes, AllowedContentTypesSelector, + SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), - sorts => sorts.GetHashCode())); + sorts => sorts.GetHashCode())); } } @@ -418,7 +340,7 @@ namespace Umbraco.Core.Models private set { _hasPropertyTypeBeenRemoved = value; - OnPropertyChanged(HasPropertyTypeBeenRemovedSelector); + OnPropertyChanged(Ps.Value.HasPropertyTypeBeenRemovedSelector); } } @@ -543,7 +465,7 @@ namespace Umbraco.Core.Models // actually remove the group PropertyGroups.RemoveItem(propertyGroupName); - OnPropertyChanged(PropertyGroupCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); } /// diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 4cf4a08bf1..e53011cc51 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -32,9 +32,13 @@ namespace Umbraco.Core.Models AddContentType(parent); } - private static readonly PropertyInfo ContentTypeCompositionSelector = - ExpressionHelper.GetPropertyInfo>( - x => x.ContentTypeComposition); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ContentTypeCompositionSelector = + ExpressionHelper.GetPropertyInfo>(x => x.ContentTypeComposition); + } /// /// Gets or sets the content types that compose this content type. @@ -46,7 +50,7 @@ namespace Umbraco.Core.Models set { _contentTypeComposition = value.ToList(); - OnPropertyChanged(ContentTypeCompositionSelector); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); } } @@ -102,7 +106,7 @@ namespace Umbraco.Core.Models throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); _contentTypeComposition.Add(contentType); - OnPropertyChanged(ContentTypeCompositionSelector); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); return true; } return false; @@ -128,7 +132,7 @@ namespace Umbraco.Core.Models if (compositionIdsToRemove.Any()) RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove); - OnPropertyChanged(ContentTypeCompositionSelector); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); return _contentTypeComposition.Remove(contentTypeComposition); } return false; diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index a52d305278..bd2cf268da 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -49,15 +49,20 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - private static readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + } /// /// Gets or sets the Id of the Parent entity @@ -67,14 +72,7 @@ namespace Umbraco.Core.Models public int ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } /// @@ -84,14 +82,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -101,14 +92,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -118,14 +102,7 @@ namespace Umbraco.Core.Models public int Level { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } /// @@ -135,14 +112,7 @@ namespace Umbraco.Core.Models public string Path //Setting this value should be handled by the class not the user { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } /// @@ -152,14 +122,7 @@ namespace Umbraco.Core.Models public int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, UserIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } } //NOTE: SD: Why do we have this ?? @@ -173,11 +136,7 @@ namespace Umbraco.Core.Models get { return _trashed; } internal set { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); + SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data _additionalData["Trashed"] = value; } @@ -189,11 +148,7 @@ namespace Umbraco.Core.Models get { return _propertyEditorAlias; } set { - SetPropertyValueAndDetectChanges(o => - { - _propertyEditorAlias = value; - return _propertyEditorAlias; - }, _propertyEditorAlias, PropertyEditorAliasSelector); + SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data _additionalData["DatabaseType"] = value; } @@ -208,12 +163,7 @@ namespace Umbraco.Core.Models get { return _databaseType; } set { - SetPropertyValueAndDetectChanges(o => - { - _databaseType = value; - return _databaseType; - }, _databaseType, DatabaseTypeSelector); - + SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data _additionalData["DatabaseType"] = value; } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 749c629d19..42b047e35b 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -30,9 +30,14 @@ namespace Umbraco.Core.Models _translations = new List(); } - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); - private static readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); + public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); + } /// /// Gets or Sets the Parent Id of the Dictionary Item @@ -41,14 +46,7 @@ namespace Umbraco.Core.Models public Guid? ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } /// @@ -58,14 +56,7 @@ namespace Umbraco.Core.Models public string ItemKey { get { return _itemKey; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _itemKey = value; - return _itemKey; - }, _itemKey, ItemKeySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); } } /// @@ -77,25 +68,21 @@ namespace Umbraco.Core.Models get { return _translations; } set { - SetPropertyValueAndDetectChanges(o => + var asArray = value.ToArray(); + //ensure the language callback is set on each translation + if (GetLanguage != null) { - var asArray = value.ToArray(); - //ensure the language callback is set on each translation - if (GetLanguage != null) + foreach (var translation in asArray.OfType()) { - foreach (var translation in asArray.OfType()) - { - translation.GetLanguage = GetLanguage; - } + translation.GetLanguage = GetLanguage; } + } - _translations = asArray; - return _translations; - }, _translations, TranslationsSelector, + SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), - enumerable => enumerable.GetHashCode())); + enumerable => enumerable.GetHashCode())); } } } diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 59f96dbe85..4e5b1c2437 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models private string _value; //note: this will be memberwise cloned private int _languageId; - + public DictionaryTranslation(ILanguage language, string value) { if (language == null) throw new ArgumentNullException("language"); @@ -50,8 +50,13 @@ namespace Umbraco.Core.Models Key = uniqueId; } - private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + } /// /// Gets or sets the for the translation @@ -79,12 +84,8 @@ namespace Umbraco.Core.Models } set { - SetPropertyValueAndDetectChanges(o => - { - _language = value; - _languageId = _language == null ? -1 : _language.Id; - return _language; - }, _language, LanguageSelector); + SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); + _languageId = _language == null ? -1 : _language.Id; } } @@ -100,14 +101,7 @@ namespace Umbraco.Core.Models public string Value { get { return _value; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _value = value; - return _value; - }, _value, ValueSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } } public override object DeepClone() diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index c4838dfd0a..637255a1c8 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -23,13 +23,17 @@ namespace Umbraco.Core.Models.EntityBase private DateTime _updateDate; private bool _wasCancelled; - private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); - private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); - private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); - private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); - private static readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo(x => x.WasCancelled); - + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); + public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); + public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); + public readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); + public readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo(x => x.WasCancelled); + } /// /// Integer Id @@ -37,18 +41,11 @@ namespace Umbraco.Core.Models.EntityBase [DataMember] public int Id { - get - { - return _id; - } + get { return _id; } set { - SetPropertyValueAndDetectChanges(o => - { - _id = value; - HasIdentity = true; //set the has Identity - return _id; - }, _id, IdSelector); + SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); + HasIdentity = true; //set the has Identity } } @@ -67,14 +64,7 @@ namespace Umbraco.Core.Models.EntityBase _key = Guid.NewGuid(); return _key; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _key = value; - return _key; - }, _key, KeySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); } } /// @@ -84,14 +74,7 @@ namespace Umbraco.Core.Models.EntityBase public DateTime CreateDate { get { return _createDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _createDate = value; - return _createDate; - }, _createDate, CreateDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); } } /// @@ -105,14 +88,7 @@ namespace Umbraco.Core.Models.EntityBase internal bool WasCancelled { get { return _wasCancelled; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _wasCancelled = value; - return _wasCancelled; - }, _wasCancelled, WasCancelledSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _wasCancelled, Ps.Value.WasCancelledSelector); } } /// @@ -122,14 +98,7 @@ namespace Umbraco.Core.Models.EntityBase public DateTime UpdateDate { get { return _updateDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _updateDate = value; - return _updateDate; - }, _updateDate, UpdateDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); } } internal virtual void ResetIdentity() @@ -166,14 +135,7 @@ namespace Umbraco.Core.Models.EntityBase { return _hasIdentity; } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _hasIdentity = value; - return _hasIdentity; - }, _hasIdentity, HasIdentitySelector); - } + protected set { SetPropertyValueAndDetectChanges(value, ref _hasIdentity, Ps.Value.HasIdentitySelector); } } //TODO: Make this NOT virtual or even exist really! diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index 8fa9bfe5ff..71a0883f9e 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -19,7 +19,9 @@ namespace Umbraco.Core.Models.EntityBase //TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662 public virtual IEnumerable GetDirtyProperties() { - return _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); + return _propertyChangedInfo == null + ? Enumerable.Empty() + : _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); } private bool _changeTrackingEnabled = true; @@ -27,12 +29,12 @@ namespace Umbraco.Core.Models.EntityBase /// /// Tracks the properties that have changed /// - private IDictionary _propertyChangedInfo = new Dictionary(); + private IDictionary _propertyChangedInfo; /// /// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties) /// - private IDictionary _lastPropertyChangedInfo = null; + private IDictionary _lastPropertyChangedInfo; /// /// Property changed event @@ -48,12 +50,13 @@ namespace Umbraco.Core.Models.EntityBase //return if we're not tracking changes if (_changeTrackingEnabled == false) return; + if (_propertyChangedInfo == null) + _propertyChangedInfo = new Dictionary(); + _propertyChangedInfo[propertyInfo.Name] = true; if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); - } + PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); } /// @@ -63,7 +66,7 @@ namespace Umbraco.Core.Models.EntityBase /// True if Property is dirty, otherwise False public virtual bool IsPropertyDirty(string propertyName) { - return _propertyChangedInfo.Any(x => x.Key == propertyName); + return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName); } /// @@ -72,7 +75,7 @@ namespace Umbraco.Core.Models.EntityBase /// True if entity is dirty, otherwise False public virtual bool IsDirty() { - return _propertyChangedInfo.Any(); + return _propertyChangedInfo != null && _propertyChangedInfo.Any(); } /// @@ -91,7 +94,7 @@ namespace Umbraco.Core.Models.EntityBase /// True if Property was changed, otherwise False. Returns false if the entity had not been previously changed. public virtual bool WasPropertyDirty(string propertyName) { - return WasDirty() && _lastPropertyChangedInfo.Any(x => x.Key == propertyName); + return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName); } /// @@ -101,7 +104,7 @@ namespace Umbraco.Core.Models.EntityBase { //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME // instance as the one on the clone, so we need to create a new instance. - _lastPropertyChangedInfo = new Dictionary(); + _lastPropertyChangedInfo = null; } /// @@ -131,26 +134,29 @@ namespace Umbraco.Core.Models.EntityBase if (rememberPreviouslyChangedProperties) { //copy the changed properties to the last changed properties - _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); + if (_propertyChangedInfo != null) + { + _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); + } } //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME // instance as the one on the clone, so we need to create a new instance. - _propertyChangedInfo = new Dictionary(); + _propertyChangedInfo = null; } - protected void ResetChangeTrackingCollections() + public void ResetChangeTrackingCollections() { - _propertyChangedInfo = new Dictionary(); - _lastPropertyChangedInfo = new Dictionary(); + _propertyChangedInfo = null; + _lastPropertyChangedInfo = null; } - protected void DisableChangeTracking() + public void DisableChangeTracking() { _changeTrackingEnabled = false; } - protected void EnableChangeTracking() + public void EnableChangeTracking() { _changeTrackingEnabled = true; } @@ -159,60 +165,61 @@ namespace Umbraco.Core.Models.EntityBase /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did /// it will ensure that the property has a dirty flag set. /// - /// - /// + /// + /// /// /// returns true if the value changed /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set + /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we + /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set /// to the same value, so it's really not dirty. /// - internal bool SetPropertyValueAndDetectChanges(Func setValue, T value, PropertyInfo propertySelector) + internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector) { if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom(typeof(T))) { throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required"); } - return SetPropertyValueAndDetectChanges(setValue, value, propertySelector, - new DelegateEqualityComparer( - //Standard Equals comparison - (arg1, arg2) => Equals(arg1, arg2), - arg => arg.GetHashCode())); - + SetPropertyValueAndDetectChanges(newVal, ref origVal, propertySelector, EqualityComparer.Default); } /// /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did /// it will ensure that the property has a dirty flag set. /// - /// - /// + /// + /// /// /// The equality comparer to use /// returns true if the value changed /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set + /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we + /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set /// to the same value, so it's really not dirty. /// - internal bool SetPropertyValueAndDetectChanges(Func setValue, T value, PropertyInfo propertySelector, IEqualityComparer comparer) + internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer comparer) { - var initVal = value; - var newVal = setValue(value); - - //don't track changes, just set the value (above) - if (_changeTrackingEnabled == false) return false; - - if (comparer.Equals(initVal, newVal) == false) + //don't track changes, just set the value + if (_changeTrackingEnabled == false) { - OnPropertyChanged(propertySelector); - return true; + //set the original value + origVal = newVal; + } + else + { + //check changed + var changed = comparer.Equals(origVal, newVal) == false; + + //set the original value + origVal = newVal; + + //raise the event if it was changed + if (changed) + { + OnPropertyChanged(propertySelector); + } } - return false; } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 8ead6da5f8..e271774b8d 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -33,8 +33,14 @@ namespace Umbraco.Core.Models _content = getFileContent != null ? null : string.Empty; } - private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + } + private string _alias; private string _name; @@ -87,11 +93,7 @@ namespace Umbraco.Core.Models _alias = null; _name = null; - SetPropertyValueAndDetectChanges(o => - { - _path = SanitizePath(value); - return _path; - }, _path, PathSelector); + SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); } } @@ -131,11 +133,9 @@ namespace Umbraco.Core.Models } set { - SetPropertyValueAndDetectChanges(o => - { - _content = value ?? string.Empty; // cannot set to null - return _content; - }, _content, ContentSelector); + SetPropertyValueAndDetectChanges( + value ?? string.Empty, // cannot set to null + ref _content, Ps.Value.ContentSelector); } } diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 2715e5fe3a..7caefc1121 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Diagnostics; using Umbraco.Core.Persistence.Mappers; @@ -21,6 +22,7 @@ namespace Umbraco.Core.Models bool Published { get; } [Obsolete("This will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] string Language { get; set; } /// diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs new file mode 100644 index 0000000000..419389f60d --- /dev/null +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a redirect url. + /// + public interface IRedirectUrl : IAggregateRoot, IRememberBeingDirty + { + /// + /// Gets or sets the identifier of the content item. + /// + [DataMember] + int ContentId { get; set; } + + /// + /// Gets or sets the unique key identifying the content item. + /// + [DataMember] + Guid ContentKey { get; set; } + + /// + /// Gets or sets the redirect url creation date. + /// + [DataMember] + DateTime CreateDateUtc { get; set; } + + /// + /// Gets or sets the redirect url route. + /// + /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. + [DataMember] + string Url { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index b23bbfb52a..d915bee85b 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -21,8 +21,13 @@ namespace Umbraco.Core.Models IsoCode = isoCode; } - private static readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); - private static readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); + public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); + } /// /// Gets or sets the Iso Code for the Language @@ -31,14 +36,7 @@ namespace Umbraco.Core.Models public string IsoCode { get { return _isoCode; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isoCode = value; - return _isoCode; - }, _isoCode, IsoCodeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); } } /// @@ -48,14 +46,7 @@ namespace Umbraco.Core.Models public string CultureName { get { return _cultureName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cultureName = value; - return _cultureName; - }, _cultureName, CultureNameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); } } /// diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index a15639f886..ef5479c316 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -114,22 +114,27 @@ namespace Umbraco.Core.Models private List _addedProperties; private List _removedProperties; - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor); - private static readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration); - private static readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage); - private static readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember); - private static readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender); - private static readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ControlType); - private static readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo(x => x.ControlAssembly); - private static readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ScriptPath); - private static readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo(x => x.XsltPath); - private static readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor); + public readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration); + public readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage); + public readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember); + public readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender); + public readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ControlType); + public readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo(x => x.ControlAssembly); + public readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ScriptPath); + public readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo(x => x.XsltPath); + public readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + } void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertiesSelector); + OnPropertyChanged(Ps.Value.PropertiesSelector); if (e.Action == NotifyCollectionChangedAction.Add) { @@ -167,7 +172,7 @@ namespace Umbraco.Core.Models /// void PropertyDataChanged(object sender, PropertyChangedEventArgs e) { - OnPropertyChanged(PropertiesSelector); + OnPropertyChanged(Ps.Value.PropertiesSelector); } public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) @@ -204,14 +209,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value.ToCleanString(CleanStringType.Alias); - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias), ref _alias, Ps.Value.AliasSelector); } } /// @@ -221,14 +219,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -238,14 +229,7 @@ namespace Umbraco.Core.Models public bool UseInEditor { get { return _useInEditor; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _useInEditor = value; - return _useInEditor; - }, _useInEditor, UseInEditorSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _useInEditor, Ps.Value.UseInEditorSelector); } } /// @@ -255,14 +239,7 @@ namespace Umbraco.Core.Models public int CacheDuration { get { return _cacheDuration; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cacheDuration = value; - return _cacheDuration; - }, _cacheDuration, CacheDurationSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cacheDuration, Ps.Value.CacheDurationSelector); } } /// @@ -272,14 +249,7 @@ namespace Umbraco.Core.Models public bool CacheByPage { get { return _cacheByPage; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cacheByPage = value; - return _cacheByPage; - }, _cacheByPage, CacheByPageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cacheByPage, Ps.Value.CacheByPageSelector); } } /// @@ -289,14 +259,7 @@ namespace Umbraco.Core.Models public bool CacheByMember { get { return _cacheByMember; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cacheByMember = value; - return _cacheByMember; - }, _cacheByMember, CacheByMemberSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cacheByMember, Ps.Value.CacheByMemberSelector); } } /// @@ -306,14 +269,7 @@ namespace Umbraco.Core.Models public bool DontRender { get { return _dontRender; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _dontRender = value; - return _dontRender; - }, _dontRender, DontRenderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _dontRender, Ps.Value.DontRenderSelector); } } /// @@ -323,14 +279,7 @@ namespace Umbraco.Core.Models public string ControlType { get { return _scriptFile; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _scriptFile = value; - return _scriptFile; - }, _scriptFile, ControlPathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _scriptFile, Ps.Value.ControlPathSelector); } } /// @@ -341,14 +290,7 @@ namespace Umbraco.Core.Models public string ControlAssembly { get { return _scriptAssembly; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _scriptAssembly = value; - return _scriptAssembly; - }, _scriptAssembly, ControlAssemblySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _scriptAssembly, Ps.Value.ControlAssemblySelector); } } /// @@ -359,14 +301,7 @@ namespace Umbraco.Core.Models public string ScriptPath { get { return _scriptPath; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _scriptPath = value; - return _scriptPath; - }, _scriptPath, ScriptPathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _scriptPath, Ps.Value.ScriptPathSelector); } } /// @@ -377,14 +312,7 @@ namespace Umbraco.Core.Models public string XsltPath { get { return _xslt; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _xslt = value; - return _xslt; - }, _xslt, XsltPathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _xslt, Ps.Value.XsltPathSelector); } } /// diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index 17fef44a87..0f29c18881 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -72,11 +72,16 @@ namespace Umbraco.Core.Models private int _id; private string _editorAlias; - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - private static readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); + } /// /// Gets or sets the Alias of the Property @@ -85,14 +90,7 @@ namespace Umbraco.Core.Models public int Id { get { return _id; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _id = value; - return _alias; - }, _alias, IdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); } } /// @@ -102,14 +100,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } /// @@ -119,14 +110,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -136,14 +120,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -159,21 +136,10 @@ namespace Umbraco.Core.Models get { return _editorAlias; } set { - SetPropertyValueAndDetectChanges(o => - { - //try to get the new mapped parameter editor - var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(value, false); - if (mapped.IsNullOrWhiteSpace() == false) - { - _editorAlias = mapped; - } - else - { - _editorAlias = value; - } - - return _editorAlias; - }, _editorAlias, PropertyTypeSelector); + //try to get the new mapped parameter editor + var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(value, false); + var newVal = mapped.IsNullOrWhiteSpace() == false ? mapped : value; + SetPropertyValueAndDetectChanges(newVal, ref _editorAlias, Ps.Value.PropertyTypeSelector); } } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index ace6c2bf67..1b206645e7 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -109,10 +109,15 @@ namespace Umbraco.Core.Models IsApproved = true; } - private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - private static readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); + public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); + public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); + public readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); + } /// /// Gets or sets the Username @@ -121,14 +126,7 @@ namespace Umbraco.Core.Models public string Username { get { return _username; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _username = value; - return _username; - }, _username, UsernameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } } /// @@ -138,14 +136,7 @@ namespace Umbraco.Core.Models public string Email { get { return _email; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _email = value; - return _email; - }, _email, EmailSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } } /// @@ -155,14 +146,7 @@ namespace Umbraco.Core.Models public string RawPasswordValue { get { return _rawPasswordValue; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _rawPasswordValue = value; - return _rawPasswordValue; - }, _rawPasswordValue, PasswordSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } } /// @@ -490,14 +474,7 @@ namespace Umbraco.Core.Models { return _providerUserKey; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _providerUserKey = value; - return _providerUserKey; - }, _providerUserKey, ProviderUserKeySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _providerUserKey, Ps.Value.ProviderUserKeySelector); } } diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index ff7e05be9e..d6dc1be557 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -21,8 +21,13 @@ namespace Umbraco.Core.Models private string _name; private int _creatorId; - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + } [DataMember] public string Name @@ -30,33 +35,22 @@ namespace Umbraco.Core.Models get { return _name; } set { - SetPropertyValueAndDetectChanges(o => + if (_name != value) { - if (_name != value) - { - //if the name has changed, add the value to the additional data, - //this is required purely for event handlers to know the previous name of the group - //so we can keep the public access up to date. - AdditionalData["previousName"] = _name; - } + //if the name has changed, add the value to the additional data, + //this is required purely for event handlers to know the previous name of the group + //so we can keep the public access up to date. + AdditionalData["previousName"] = _name; + } - _name = value; - return _name; - }, _name, NameSelector); + SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } public int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } public IDictionary AdditionalData { get; private set; } diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 9000a33b54..4f79e1d231 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -30,7 +30,12 @@ namespace Umbraco.Core.Models MemberTypePropertyTypes = new Dictionary(); } - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + } /// /// The Alias of the ContentType @@ -50,13 +55,11 @@ namespace Umbraco.Core.Models // .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase) // Need to ask Stephen - SetPropertyValueAndDetectChanges(o => - { - _alias = value == "_umbracoSystemDefaultProtectType" - ? value - : (value == null ? string.Empty : value.ToSafeAlias() ); - return _alias; - }, _alias, AliasSelector); + var newVal = value == "_umbracoSystemDefaultProtectType" + ? value + : (value == null ? string.Empty : value.ToSafeAlias()); + + SetPropertyValueAndDetectChanges(newVal, ref _alias, Ps.Value.AliasSelector); } } diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index c743893037..cadda14508 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -77,27 +77,32 @@ namespace Umbraco.Core.Models.Membership private bool _defaultToLiveEditing; - private static readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo(x => x.FailedPasswordAttempts); - private static readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLockoutDate); - private static readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDate); - private static readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); + private static readonly Lazy Ps = new Lazy(); - private static readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); - private static readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); - private static readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); - private static readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); - private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - - private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - private static readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo(x => x.IsLockedOut); - private static readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); - private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + private class PropertySelectors + { + public readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo(x => x.FailedPasswordAttempts); + public readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLockoutDate); + public readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDate); + public readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); - private static readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); - private static readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.UserType); + public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); + public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); + public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); + public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); + public readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + + public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); + public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); + public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); + public readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo(x => x.IsLockedOut); + public readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + + public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); + public readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.UserType); + } #region Implementation of IMembershipUser @@ -113,124 +118,61 @@ namespace Umbraco.Core.Models.Membership public string Username { get { return _username; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _username = value; - return _username; - }, _username, UsernameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } } [DataMember] public string Email { get { return _email; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _email = value; - return _email; - }, _email, EmailSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } } [DataMember] public string RawPasswordValue { get { return _rawPasswordValue; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _rawPasswordValue = value; - return _rawPasswordValue; - }, _rawPasswordValue, PasswordSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } } [DataMember] public bool IsApproved { get { return _isApproved; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isApproved = value; - return _isApproved; - }, _isApproved, IsApprovedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isApproved, Ps.Value.IsApprovedSelector); } } [IgnoreDataMember] public bool IsLockedOut { get { return _isLockedOut; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isLockedOut = value; - return _isLockedOut; - }, _isLockedOut, IsLockedOutSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isLockedOut, Ps.Value.IsLockedOutSelector); } } [IgnoreDataMember] public DateTime LastLoginDate { get { return _lastLoginDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _lastLoginDate = value; - return _lastLoginDate; - }, _lastLoginDate, LastLoginDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, Ps.Value.LastLoginDateSelector); } } [IgnoreDataMember] public DateTime LastPasswordChangeDate { get { return _lastPasswordChangedDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _lastPasswordChangedDate = value; - return _lastPasswordChangedDate; - }, _lastPasswordChangedDate, LastPasswordChangeDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, Ps.Value.LastPasswordChangeDateSelector); } } [IgnoreDataMember] public DateTime LastLockoutDate { get { return _lastLockoutDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _lastLockoutDate = value; - return _lastLockoutDate; - }, _lastLockoutDate, LastLockoutDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, Ps.Value.LastLockoutDateSelector); } } [IgnoreDataMember] public int FailedPasswordAttempts { get { return _failedLoginAttempts; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _failedLoginAttempts = value; - return _failedLoginAttempts; - }, _failedLoginAttempts, FailedPasswordAttemptsSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, Ps.Value.FailedPasswordAttemptsSelector); } } //TODO: Figure out how to support all of this! - we cannot have NotImplementedExceptions because these get used by the IMembershipMemberService service so @@ -251,14 +193,7 @@ namespace Umbraco.Core.Models.Membership public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } public IEnumerable AllowedSections @@ -294,14 +229,7 @@ namespace Umbraco.Core.Models.Membership public string SecurityStamp { get { return _securityStamp; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _securityStamp = value; - return _securityStamp; - }, _securityStamp, SecurityStampSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); } } /// @@ -330,14 +258,7 @@ namespace Umbraco.Core.Models.Membership public int SessionTimeout { get { return _sessionTimeout; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sessionTimeout = value; - return _sessionTimeout; - }, _sessionTimeout, SessionTimeoutSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, Ps.Value.SessionTimeoutSelector); } } /// @@ -350,14 +271,7 @@ namespace Umbraco.Core.Models.Membership public int StartContentId { get { return _startContentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _startContentId = value; - return _startContentId; - }, _startContentId, StartContentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); } } /// @@ -370,28 +284,14 @@ namespace Umbraco.Core.Models.Membership public int StartMediaId { get { return _startMediaId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _startMediaId = value; - return _startMediaId; - }, _startMediaId, StartMediaIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); } } [DataMember] public string Language { get { return _language; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _language = value; - return _language; - }, _language, LanguageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } } //TODO: This should be a private set @@ -406,14 +306,7 @@ namespace Umbraco.Core.Models.Membership internal bool DefaultToLiveEditing { get { return _defaultToLiveEditing; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _defaultToLiveEditing = value; - return _defaultToLiveEditing; - }, _defaultToLiveEditing, DefaultToLiveEditingSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _defaultToLiveEditing, Ps.Value.DefaultToLiveEditingSelector); } } [IgnoreDataMember] @@ -427,11 +320,7 @@ namespace Umbraco.Core.Models.Membership throw new InvalidOperationException("Cannot assign a User Type that has not been persisted"); } - SetPropertyValueAndDetectChanges(o => - { - _userType = value; - return _userType; - }, _userType, UserTypeSelector); + SetPropertyValueAndDetectChanges(value, ref _userType, Ps.Value.UserTypeSelector); } } @@ -457,7 +346,7 @@ namespace Umbraco.Core.Models.Membership /// void SectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(AllowedSectionsSelector); + OnPropertyChanged(Ps.Value.AllowedSectionsSelector); if (e.Action == NotifyCollectionChangedAction.Add) { diff --git a/src/Umbraco.Core/Models/Membership/UserType.cs b/src/Umbraco.Core/Models/Membership/UserType.cs index e312b09951..f519a484a0 100644 --- a/src/Umbraco.Core/Models/Membership/UserType.cs +++ b/src/Umbraco.Core/Models/Membership/UserType.cs @@ -20,9 +20,14 @@ namespace Umbraco.Core.Models.Membership private string _name; private IEnumerable _permissions; - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + } public UserType(string name, string alias) { @@ -40,11 +45,10 @@ namespace Umbraco.Core.Models.Membership get { return _alias; } set { - SetPropertyValueAndDetectChanges(o => - { - _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); - return _alias; - }, _alias, AliasSelector); + SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); } } @@ -52,14 +56,7 @@ namespace Umbraco.Core.Models.Membership public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -74,11 +71,7 @@ namespace Umbraco.Core.Models.Membership get { return _permissions; } set { - SetPropertyValueAndDetectChanges(o => - { - _permissions = value; - return _permissions; - }, _permissions, PermissionsSelector, + SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), diff --git a/src/Umbraco.Core/Models/MigrationEntry.cs b/src/Umbraco.Core/Models/MigrationEntry.cs index e756e92629..575ee8efc3 100644 --- a/src/Umbraco.Core/Models/MigrationEntry.cs +++ b/src/Umbraco.Core/Models/MigrationEntry.cs @@ -19,35 +19,27 @@ namespace Umbraco.Core.Models _version = version; } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.MigrationName); - private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.MigrationName); + public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + } + private string _migrationName; private SemVersion _version; public string MigrationName { get { return _migrationName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _migrationName = value; - return _migrationName; - }, _migrationName, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _migrationName, Ps.Value.NameSelector); } } public SemVersion Version { get { return _version; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _version = value; - return _version; - }, _version, VersionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index f3fbefda4f..e5471217a5 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -1,11 +1,9 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { @@ -45,8 +43,42 @@ namespace Umbraco.Core.Models Value = value; } - private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + } + + private static readonly DelegateEqualityComparer ValueComparer = new DelegateEqualityComparer( + (o, o1) => + { + if (o == null && o1 == null) return true; + + //custom comparer for strings. + if (o is string || o1 is string) + { + //if one is null and another is empty then they are the same + if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + { + return true; + } + if (o == null || o1 == null) return false; + return o.Equals(o1); + } + + if (o == null || o1 == null) return false; + + //Custom comparer for enumerable if it is enumerable + var enum1 = o as IEnumerable; + var enum2 = o1 as IEnumerable; + if (enum1 != null && enum2 != null) + { + return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); + } + return o.Equals(o1); + }, o => o.GetHashCode()); /// /// Returns the instance of the tag support, by default tags are not enabled @@ -98,14 +130,14 @@ namespace Umbraco.Core.Models public Guid Version { get { return _version; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _version = value; - return _version; - }, _version, VersionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } + } + + private static void ThrowTypeException(object value, Type expected, string alias) + { + throw new Exception(string.Format("Value \"{0}\" of type \"{1}\" could not be converted" + + " to type \"{2}\" which is expected by property type \"{3}\".", + value, value.GetType(), expected, alias)); } /// @@ -121,91 +153,54 @@ namespace Umbraco.Core.Models get { return _value; } set { - bool typeValidation = _propertyType.IsPropertyTypeValid(value); + var isOfExpectedType = _propertyType.IsPropertyTypeValid(value); - if (typeValidation == false) + if (isOfExpectedType == false) // isOfExpectedType is true if value is null - so if false, value is *not* null { - // Normally we'll throw an exception here. However if the property is of a type that can have it's data field (dataInt, dataVarchar etc.) - // changed, we might have a value of the now "wrong" type. As of May 2016 Label is the only built-in property editor that supports this. - // In that case we should try to parse the value and return null if that's not possible rather than throwing an exception. - if (value != null && _propertyType.CanHaveDataValueTypeChanged()) - { - var stringValue = value.ToString(); - switch (_propertyType.DataTypeDatabaseType) - { - case DataTypeDatabaseType.Nvarchar: - case DataTypeDatabaseType.Ntext: - value = stringValue; - break; - case DataTypeDatabaseType.Integer: - int integerValue; - if (int.TryParse(stringValue, out integerValue) == false) - { - // Edge case, but if changed from decimal --> integer, the above tryparse will fail. So we'll try going - // via decimal too to return the integer value rather than zero. - decimal decimalForIntegerValue; - if (decimal.TryParse(stringValue, out decimalForIntegerValue)) - { - integerValue = (int)decimalForIntegerValue; - } - } + // "garbage-in", accept what we can & convert + // throw only if conversion is not possible - value = integerValue; - break; - case DataTypeDatabaseType.Decimal: - decimal decimalValue; - decimal.TryParse(stringValue, out decimalValue); - value = decimalValue; - break; - case DataTypeDatabaseType.Date: - DateTime dateValue; - DateTime.TryParse(stringValue, out dateValue); - value = dateValue; - break; - } - } - else + var s = value.ToString(); + + switch (_propertyType.DataTypeDatabaseType) { - throw new Exception( - string.Format( - "Type validation failed. The value type: '{0}' does not match the DataType in PropertyType with alias: '{1}'", - value == null ? "null" : value.GetType().Name, Alias)); + case DataTypeDatabaseType.Nvarchar: + case DataTypeDatabaseType.Ntext: + value = s; + break; + case DataTypeDatabaseType.Integer: + if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null + else + { + var convInt = value.TryConvertTo(); + if (convInt == false) ThrowTypeException(value, typeof(int), _propertyType.Alias); + value = convInt.Result; + } + break; + case DataTypeDatabaseType.Decimal: + if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null + else + { + var convDecimal = value.TryConvertTo(); + if (convDecimal == false) ThrowTypeException(value, typeof (decimal), _propertyType.Alias); + // need to normalize the value (change the scaling factor and remove trailing zeroes) + // because the underlying database is going to mess with the scaling factor anyways. + value = convDecimal.Result.Normalize(); + } + break; + case DataTypeDatabaseType.Date: + if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null + else + { + var convDateTime = value.TryConvertTo(); + if (convDateTime == false) ThrowTypeException(value, typeof (DateTime), _propertyType.Alias); + value = convDateTime.Result; + } + break; } } - SetPropertyValueAndDetectChanges(o => - { - _value = value; - return _value; - }, _value, ValueSelector, - new DelegateEqualityComparer( - (o, o1) => - { - if (o == null && o1 == null) return true; - - //custom comparer for strings. - if (o is string || o1 is string) - { - //if one is null and another is empty then they are the same - if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) - { - return true; - } - if (o == null || o1 == null) return false; - return o.Equals(o1); - } - - if (o == null || o1 == null) return false; - - //Custom comparer for enumerable if it is enumerable - var enum1 = o as IEnumerable; - var enum2 = o1 as IEnumerable; - if (enum1 != null && enum2 != null) - { - return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); - } - return o.Equals(o1); - }, o => o.GetHashCode())); + SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, ValueComparer); } } diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index de88012c0e..68f6599e2e 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -29,12 +29,18 @@ namespace Umbraco.Core.Models PropertyTypes = propertyTypeCollection; } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private readonly static PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); + } + void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyTypeCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); } /// @@ -44,14 +50,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -61,14 +60,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 846b907197..0aebcb6544 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -46,9 +46,9 @@ namespace Umbraco.Core.Models public PropertyType(IDataTypeDefinition dataTypeDefinition, string propertyTypeAlias) : this(dataTypeDefinition) { - SetAlias(propertyTypeAlias); + _alias = GetAlias(propertyTypeAlias); } - + public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType) : this(propertyEditorAlias, dataTypeDatabaseType, false) { @@ -56,7 +56,7 @@ namespace Umbraco.Core.Models public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType, string propertyTypeAlias) : this(propertyEditorAlias, dataTypeDatabaseType, false, propertyTypeAlias) - { + { } /// @@ -84,20 +84,25 @@ namespace Umbraco.Core.Models _isExplicitDbType = isExplicitDbType; _propertyEditorAlias = propertyEditorAlias; _dataTypeDatabaseType = dataTypeDatabaseType; - SetAlias(propertyTypeAlias); + _alias = GetAlias(propertyTypeAlias); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); - private static readonly PropertyInfo DataTypeDefinitionIdSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDefinitionId); - private static readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - private static readonly PropertyInfo DataTypeDatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDatabaseType); - private static readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); - private static readonly PropertyInfo HelpTextSelector = ExpressionHelper.GetPropertyInfo(x => x.HelpText); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo ValidationRegExpSelector = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); - private static readonly PropertyInfo PropertyGroupIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); + public readonly PropertyInfo DataTypeDefinitionIdSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDefinitionId); + public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo DataTypeDatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDatabaseType); + public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); + public readonly PropertyInfo HelpTextSelector = ExpressionHelper.GetPropertyInfo(x => x.HelpText); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo ValidationRegExpSelector = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); + public readonly PropertyInfo PropertyGroupIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); + } /// /// Gets of Sets the Name of the PropertyType @@ -106,14 +111,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -123,14 +121,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - SetAlias(value); - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(GetAlias(value), ref _alias, Ps.Value.AliasSelector); } } /// @@ -140,14 +131,7 @@ namespace Umbraco.Core.Models public string Description { get { return _description; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _description = value; - return _description; - }, _description, DescriptionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); } } /// @@ -158,28 +142,14 @@ namespace Umbraco.Core.Models public int DataTypeDefinitionId { get { return _dataTypeDefinitionId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _dataTypeDefinitionId = value; - return _dataTypeDefinitionId; - }, _dataTypeDefinitionId, DataTypeDefinitionIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _dataTypeDefinitionId, Ps.Value.DataTypeDefinitionIdSelector); } } [DataMember] public string PropertyEditorAlias { get { return _propertyEditorAlias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _propertyEditorAlias = value; - return _propertyEditorAlias; - }, _propertyEditorAlias, PropertyEditorAliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); } } /// @@ -213,11 +183,7 @@ namespace Umbraco.Core.Models //don't allow setting this if an explicit declaration has been made in the ctor if (_isExplicitDbType) return; - SetPropertyValueAndDetectChanges(o => - { - _dataTypeDatabaseType = value; - return _dataTypeDatabaseType; - }, _dataTypeDatabaseType, DataTypeDatabaseTypeSelector); + SetPropertyValueAndDetectChanges(value, ref _dataTypeDatabaseType, Ps.Value.DataTypeDatabaseTypeSelector); } } @@ -229,14 +195,7 @@ namespace Umbraco.Core.Models internal Lazy PropertyGroupId { get { return _propertyGroupId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _propertyGroupId = value; - return _propertyGroupId; - }, _propertyGroupId, PropertyGroupIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Ps.Value.PropertyGroupIdSelector); } } /// @@ -246,14 +205,7 @@ namespace Umbraco.Core.Models public bool Mandatory { get { return _mandatory; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _mandatory = value; - return _mandatory; - }, _mandatory, MandatorySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); } } /// @@ -264,14 +216,7 @@ namespace Umbraco.Core.Models public string HelpText { get { return _helpText; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _helpText = value; - return _helpText; - }, _helpText, HelpTextSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _helpText, Ps.Value.HelpTextSelector); } } /// @@ -281,14 +226,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -298,23 +236,16 @@ namespace Umbraco.Core.Models public string ValidationRegExp { get { return _validationRegExp; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _validationRegExp = value; - return _validationRegExp; - }, _validationRegExp, ValidationRegExpSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Ps.Value.ValidationRegExpSelector); } } - private void SetAlias(string value) + private string GetAlias(string value) { //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix // which is used internally - _alias = value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) + return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) ? value : value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); } @@ -367,18 +298,21 @@ namespace Umbraco.Core.Models } /// - /// Validates the Value from a Property according to its type + /// Gets a value indicating whether the value is of the expected type + /// for the property, and can be assigned to the property "as is". /// - /// - /// True if valid, otherwise false + /// The value. + /// True if the value is of the expected type for the property, + /// and can be assigned to the property "as is". Otherwise, false, to indicate + /// that some conversion is required. public bool IsPropertyTypeValid(object value) { - //Can't validate null values, so just allow it to pass the current validation + // null values are assumed to be ok if (value == null) return true; - //Check type if the type of the value match the type from the DataType/PropertyEditor - Type type = value.GetType(); + // check if the type of the value matches the type from the DataType/PropertyEditor + var valueType = value.GetType(); //TODO Add PropertyEditor Type validation when its relevant to introduce /*bool isEditorModel = value is IEditorModel; @@ -395,50 +329,34 @@ namespace Umbraco.Core.Models return argument == type; }*/ - if (PropertyEditorAlias.IsNullOrWhiteSpace() == false) + if (PropertyEditorAlias.IsNullOrWhiteSpace() == false) // fixme - always true? { - //Find DataType by Id - //IDataType dataType = DataTypesResolver.Current.GetById(DataTypeControlId); - //Check if dataType is null (meaning that the ControlId is valid) ? - //Possibly cast to BaseDataType and get the DbType from there (which might not be possible because it lives in umbraco.cms.businesslogic.datatype) ? - - //Simple validation using the DatabaseType from the DataTypeDefinition and Type of the passed in value - if (DataTypeDatabaseType == DataTypeDatabaseType.Integer && type == typeof(int)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Decimal && type == typeof(decimal)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Date && type == typeof(DateTime)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar && type == typeof(string)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Ntext && type == typeof(string)) - return true; + // simple validation using the DatabaseType from the DataTypeDefinition + // and the Type of the passed in value + switch (DataTypeDatabaseType) + { + // fixme breaking! + case DataTypeDatabaseType.Integer: + return valueType == typeof(int); + case DataTypeDatabaseType.Decimal: + return valueType == typeof(decimal); + case DataTypeDatabaseType.Date: + return valueType == typeof(DateTime); + case DataTypeDatabaseType.Nvarchar: + return valueType == typeof(string); + case DataTypeDatabaseType.Ntext: + return valueType == typeof(string); + } } - //Fallback for simple value types when no Control Id or Database Type is set - if (type.IsPrimitive || value is string) + // fixme - never reached + makes no sense? + // fallback for simple value types when no Control Id or Database Type is set + if (valueType.IsPrimitive || value is string) return true; return false; } - /// - /// Checks the underlying property editor prevalues to see if the one that allows changing of the database field - /// to which data is saved (dataInt, dataVarchar etc.) is included. If so that means the field could be changed when the data - /// type is saved. - /// - /// - internal bool CanHaveDataValueTypeChanged() - { - var propertyEditor = PropertyEditorResolver.Current.GetByAlias(_propertyEditorAlias); - return propertyEditor.PreValueEditor.Fields - .SingleOrDefault(x => x.Key == Constants.PropertyEditors.PreValueKeys.DataValueType) != null; - } - /// /// Validates the Value from a Property according to the validation settings /// @@ -458,15 +376,15 @@ namespace Umbraco.Core.Models var regexPattern = new Regex(ValidationRegExp); return regexPattern.IsMatch(value.ToString()); } - catch + catch { throw new Exception(string .Format("Invalid validation expression on property {0}",this.Alias)); } - + } - + //TODO: We must ensure that the property value can actually be saved based on the specified database type - + //TODO Add PropertyEditor validation when its relevant to introduce /*if (value is IEditorModel && DataTypeControlId != Guid.Empty) { @@ -484,19 +402,19 @@ namespace Umbraco.Core.Models { if (base.Equals(other)) return true; - //Check whether the PropertyType's properties are equal. + //Check whether the PropertyType's properties are equal. return Alias.InvariantEquals(other.Alias); } public override int GetHashCode() { - //Get hash code for the Name field if it is not null. + //Get hash code for the Name field if it is not null. int baseHash = base.GetHashCode(); - //Get hash code for the Alias field. + //Get hash code for the Alias field. int hashAlias = Alias.ToLowerInvariant().GetHashCode(); - //Calculate the hash code for the product. + //Calculate the hash code for the product. return baseHash ^ hashAlias; } @@ -508,7 +426,7 @@ namespace Umbraco.Core.Models //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { - clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); + clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); } //this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 9e12d6ab57..58e3b69394 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Models void _ruleCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(AllowedSectionsSelector); + OnPropertyChanged(Ps.Value.AllowedSectionsSelector); //if (e.Action == NotifyCollectionChangedAction.Add) //{ @@ -68,10 +68,15 @@ namespace Umbraco.Core.Models } } - private static readonly PropertyInfo ProtectedNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ProtectedNodeId); - private static readonly PropertyInfo LoginNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.LoginNodeId); - private static readonly PropertyInfo NoAccessNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NoAccessNodeId); - private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Rules); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ProtectedNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ProtectedNodeId); + public readonly PropertyInfo LoginNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.LoginNodeId); + public readonly PropertyInfo NoAccessNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NoAccessNodeId); + public readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Rules); + } internal IEnumerable RemovedRules { @@ -112,42 +117,21 @@ namespace Umbraco.Core.Models public int LoginNodeId { get { return _loginNodeId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _loginNodeId = value; - return _loginNodeId; - }, _loginNodeId, LoginNodeIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _loginNodeId, Ps.Value.LoginNodeIdSelector); } } [DataMember] public int NoAccessNodeId { get { return _noAccessNodeId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _noAccessNodeId = value; - return _noAccessNodeId; - }, _noAccessNodeId, NoAccessNodeIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, Ps.Value.NoAccessNodeIdSelector); } } [DataMember] public int ProtectedNodeId { get { return _protectedNodeId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _protectedNodeId = value; - return _protectedNodeId; - }, _protectedNodeId, ProtectedNodeIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, Ps.Value.ProtectedNodeIdSelector); } } public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs index c785d028d0..ed2cbdbd48 100644 --- a/src/Umbraco.Core/Models/PublicAccessRule.cs +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -23,35 +23,26 @@ namespace Umbraco.Core.Models { } - private static readonly PropertyInfo RuleValueSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleValue); - private static readonly PropertyInfo RuleTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleType); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo RuleValueSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleValue); + public readonly PropertyInfo RuleTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleType); + } public Guid AccessEntryId { get; internal set; } public string RuleValue { get { return _ruleValue; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _ruleValue = value; - return _ruleValue; - }, _ruleValue, RuleValueSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _ruleValue, Ps.Value.RuleValueSelector); } } public string RuleType { get { return _ruleType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _ruleType = value; - return _ruleType; - }, _ruleType, RuleTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _ruleType, Ps.Value.RuleTypeSelector); } } diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 8840cb771d..b385381e5b 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("contentNodeId")] [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData_1")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyData_1", ForColumns = "contentNodeId,versionId,propertytypeid")] public int NodeId { get; set; } [Column("versionId")] @@ -34,9 +34,23 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public int? Integer { get; set; } + private decimal? _decimalValue; + [Column("dataDecimal")] [NullSetting(NullSetting = NullSettings.Null)] - public decimal? Decimal { get; set; } + public decimal? Decimal + { + get + { + return _decimalValue; + } + set + { + // need to normalize the value (change the scaling factor and remove trailing zeroes) + // because the underlying database probably has messed with the scaling factor. + _decimalValue = value.HasValue ? (decimal?) value.Value.Normalize() : null; + } + } [Column("dataDate")] [NullSetting(NullSetting = NullSettings.Null)] diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs index ac38b7b642..fb5d833036 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs @@ -1,4 +1,5 @@ -using NPoco; +using System; +using NPoco; using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms @@ -19,5 +20,8 @@ namespace Umbraco.Core.Models.Rdbms [Column("contenttypeNodeId")] public int ContentTypeNodeId { get; set; } + + [Column("PropertyGroupUniqueID")] + public Guid UniqueId { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs index bba1546a93..36ac4e1c3c 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs @@ -52,5 +52,8 @@ namespace Umbraco.Core.Models.Rdbms [Column("dbType")] public string DbType { get; set; } + + [Column("UniqueID")] + public Guid UniqueId { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs new file mode 100644 index 0000000000..e709532b91 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs @@ -0,0 +1,50 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoRedirectUrl")] + [PrimaryKey("id")] + [ExplicitColumns] + class RedirectUrlDto + { + public RedirectUrlDto() + { + CreateDateUtc = DateTime.UtcNow; + } + + // notes + // + // we want a unique, non-clustered index on (url ASC, contentId ASC, createDate DESC) but the + // problem is that the index key must be 900 bytes max. should we run without an index? done + // some perfs comparisons, and running with an index on a hash is only slightly slower on + // inserts, and much faster on reads, so... we have an index on a hash. + + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 1, Name = "PK_umbracoRedirectUrl")] + public int Id { get; set; } + + [ResultColumn] + public int ContentId { get; set; } + + [Column("contentKey")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [ForeignKey(typeof(NodeDto), Column = "uniqueID")] + public Guid ContentKey { get; set; } + + [Column("createDateUtc")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public DateTime CreateDateUtc { get; set; } + + [Column("url")] + [NullSetting(NullSetting = NullSettings.NotNull)] + //[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "url, createDateUtc")] + public string Url { get; set; } + + [Column("urlHash")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, createDateUtc")] + public string UrlHash { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs index 91132d3a7e..6ff7c0bc00 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs @@ -1,12 +1,11 @@ -using NPoco; +using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { - [TableName("cmsStylesheet")] - [PrimaryKey("nodeId", AutoIncrement = false)] - [ExplicitColumns] + [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] internal class StylesheetDto { [Column("nodeId")] diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs b/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs index 8e249a8da2..0d4c2ec3fa 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs @@ -1,12 +1,11 @@ -using NPoco; +using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { - [TableName("cmsStylesheetProperty")] - [PrimaryKey("nodeId", AutoIncrement = false)] - [ExplicitColumns] + [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] internal class StylesheetPropertyDto { [Column("nodeId")] diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs new file mode 100644 index 0000000000..ff7746d0cf --- /dev/null +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -0,0 +1,67 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + /// + /// Implements . + /// + [Serializable] + [DataContract(IsReference = true)] + public class RedirectUrl : Entity, IRedirectUrl + { + /// + /// Initializes a new instance of the class. + /// + public RedirectUrl() + { + CreateDateUtc = DateTime.UtcNow; + } + + private static readonly Lazy Ps = new Lazy(); + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo ContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentId); + public readonly PropertyInfo ContentKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ContentKey); + public readonly PropertyInfo CreateDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDateUtc); + public readonly PropertyInfo UrlSelector = ExpressionHelper.GetPropertyInfo(x => x.Url); + } + + private int _contentId; + private Guid _contentKey; + private DateTime _createDateUtc; + private string _url; + + /// + public int ContentId + { + get { return _contentId; } + set { SetPropertyValueAndDetectChanges(value, ref _contentId, Ps.Value.ContentIdSelector); } + } + + /// + public Guid ContentKey + { + get { return _contentKey; } + set { SetPropertyValueAndDetectChanges(value, ref _contentKey, Ps.Value.ContentKeySelector); } + } + + /// + public DateTime CreateDateUtc + { + get { return _createDateUtc; } + set { SetPropertyValueAndDetectChanges(value, ref _createDateUtc, Ps.Value.CreateDateUtcSelector); } + } + + /// + public string Url + { + get { return _url; } + set { SetPropertyValueAndDetectChanges(value, ref _url, Ps.Value.UrlSelector); } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index f08e4b147e..6d383f9884 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -26,10 +26,15 @@ namespace Umbraco.Core.Models _relationType = relationType; } - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo ChildIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildId); - private static readonly PropertyInfo RelationTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RelationType); - private static readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo ChildIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildId); + public readonly PropertyInfo RelationTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RelationType); + public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + } /// /// Gets or sets the Parent Id of the Relation (Source) @@ -38,14 +43,7 @@ namespace Umbraco.Core.Models public int ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } /// @@ -55,14 +53,7 @@ namespace Umbraco.Core.Models public int ChildId { get { return _childId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _childId = value; - return _childId; - }, _childId, ChildIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _childId, Ps.Value.ChildIdSelector); } } /// @@ -72,14 +63,7 @@ namespace Umbraco.Core.Models public IRelationType RelationType { get { return _relationType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _relationType = value; - return _relationType; - }, _relationType, RelationTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _relationType, Ps.Value.RelationTypeSelector); } } /// @@ -89,14 +73,7 @@ namespace Umbraco.Core.Models public string Comment { get { return _comment; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _comment = value; - return _comment; - }, _comment, CommentSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } } /// diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 5477628222..04ef897c1f 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -35,11 +35,16 @@ namespace Umbraco.Core.Models Name = name; } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo IsBidirectionalSelector = ExpressionHelper.GetPropertyInfo(x => x.IsBidirectional); - private static readonly PropertyInfo ParentObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentObjectType); - private static readonly PropertyInfo ChildObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildObjectType); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo IsBidirectionalSelector = ExpressionHelper.GetPropertyInfo(x => x.IsBidirectional); + public readonly PropertyInfo ParentObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentObjectType); + public readonly PropertyInfo ChildObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildObjectType); + } /// /// Gets or sets the Name of the RelationType @@ -48,14 +53,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -65,14 +63,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } /// @@ -82,14 +73,7 @@ namespace Umbraco.Core.Models public bool IsBidirectional { get { return _isBidrectional; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isBidrectional = value; - return _isBidrectional; - }, _isBidrectional, IsBidirectionalSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isBidrectional, Ps.Value.IsBidirectionalSelector); } } /// @@ -100,14 +84,7 @@ namespace Umbraco.Core.Models public Guid ParentObjectType { get { return _parentObjectType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentObjectType = value; - return _parentObjectType; - }, _parentObjectType, ParentObjectTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentObjectType, Ps.Value.ParentObjectTypeSelector); } } /// @@ -118,14 +95,7 @@ namespace Umbraco.Core.Models public Guid ChildObjectType { get { return _childObjectType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _childObjectType = value; - return _childObjectType; - }, _childObjectType, ChildObjectTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _childObjectType, Ps.Value.ChildObjectTypeSelector); } } } diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index cee70893d0..f68f21d759 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -15,10 +15,15 @@ namespace Umbraco.Core.Models private bool _isActive; private bool _isMaster; - private static readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); - private static readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); - private static readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); - private static readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); + public readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); + public readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); + public readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); + } /// /// Initialiazes a new instance of the class. @@ -69,14 +74,7 @@ namespace Umbraco.Core.Models public string ServerAddress { get { return _serverAddress; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _serverAddress = value; - return _serverAddress; - }, _serverAddress, ServerAddressSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _serverAddress, Ps.Value.ServerAddressSelector); } } /// @@ -85,14 +83,7 @@ namespace Umbraco.Core.Models public string ServerIdentity { get { return _serverIdentity; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _serverIdentity = value; - return _serverIdentity; - }, _serverIdentity, ServerIdentitySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _serverIdentity, Ps.Value.ServerIdentitySelector); } } /// @@ -101,14 +92,7 @@ namespace Umbraco.Core.Models public bool IsActive { get { return _isActive; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isActive = value; - return _isActive; - }, _isActive, IsActiveSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isActive, Ps.Value.IsActiveSelector); } } /// @@ -117,14 +101,7 @@ namespace Umbraco.Core.Models public bool IsMaster { get { return _isMaster; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isMaster = value; - return _isMaster; - }, _isMaster, IsMasterSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isMaster, Ps.Value.IsMasterSelector); } } /// diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index fead6f033a..b0b2144044 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -25,8 +25,13 @@ namespace Umbraco.Core.Models _value = value; } - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + } /// /// The CSS rule name that can be used by Umbraco in the back office @@ -39,14 +44,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } /// @@ -55,14 +53,7 @@ namespace Umbraco.Core.Models public string Value { get { return _value; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _value = value; - return _value; - }, _value, ValueSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } } } diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index bfe4c10f42..92dfb9dee7 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -27,35 +27,27 @@ namespace Umbraco.Core.Models NodeCount = nodeCount; } - private static readonly PropertyInfo TextSelector = ExpressionHelper.GetPropertyInfo(x => x.Text); - private static readonly PropertyInfo GroupSelector = ExpressionHelper.GetPropertyInfo(x => x.Group); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo TextSelector = ExpressionHelper.GetPropertyInfo(x => x.Text); + public readonly PropertyInfo GroupSelector = ExpressionHelper.GetPropertyInfo(x => x.Group); + } + private string _text; private string _group; public string Text { get { return _text; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _text = value; - return _text; - }, _text, TextSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _text, Ps.Value.TextSelector); } } public string Group { get { return _group; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _group = value; - return _group; - }, _group, GroupSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _group, Ps.Value.GroupSelector); } } public int NodeCount { get; internal set; } diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs index 0f58814eea..fc6350a13b 100644 --- a/src/Umbraco.Core/Models/Task.cs +++ b/src/Umbraco.Core/Models/Task.cs @@ -24,12 +24,17 @@ namespace Umbraco.Core.Models _taskType = taskType; } - private static readonly PropertyInfo ClosedSelector = ExpressionHelper.GetPropertyInfo(x => x.Closed); - private static readonly PropertyInfo TaskTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.TaskType); - private static readonly PropertyInfo EntityIdSelector = ExpressionHelper.GetPropertyInfo(x => x.EntityId); - private static readonly PropertyInfo OwnerUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.OwnerUserId); - private static readonly PropertyInfo AssigneeUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.AssigneeUserId); - private static readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ClosedSelector = ExpressionHelper.GetPropertyInfo(x => x.Closed); + public readonly PropertyInfo TaskTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.TaskType); + public readonly PropertyInfo EntityIdSelector = ExpressionHelper.GetPropertyInfo(x => x.EntityId); + public readonly PropertyInfo OwnerUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.OwnerUserId); + public readonly PropertyInfo AssigneeUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.AssigneeUserId); + public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + } /// /// Gets or sets a boolean indicating whether the task is closed @@ -38,14 +43,7 @@ namespace Umbraco.Core.Models public bool Closed { get { return _closed; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _closed = value; - return _closed; - }, _closed, ClosedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _closed, Ps.Value.ClosedSelector); } } /// @@ -55,14 +53,7 @@ namespace Umbraco.Core.Models public TaskType TaskType { get { return _taskType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _taskType = value; - return _taskType; - }, _taskType, TaskTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _taskType, Ps.Value.TaskTypeSelector); } } /// @@ -72,14 +63,7 @@ namespace Umbraco.Core.Models public int EntityId { get { return _entityId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _entityId = value; - return _entityId; - }, _entityId, EntityIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _entityId, Ps.Value.EntityIdSelector); } } /// @@ -89,14 +73,7 @@ namespace Umbraco.Core.Models public int OwnerUserId { get { return _ownerUserId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _ownerUserId = value; - return _ownerUserId; - }, _ownerUserId, OwnerUserIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _ownerUserId, Ps.Value.OwnerUserIdSelector); } } /// @@ -106,14 +83,7 @@ namespace Umbraco.Core.Models public int AssigneeUserId { get { return _assigneeUserId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _assigneeUserId = value; - return _assigneeUserId; - }, _assigneeUserId, AssigneeUserIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _assigneeUserId, Ps.Value.AssigneeUserIdSelector); } } /// @@ -123,14 +93,7 @@ namespace Umbraco.Core.Models public string Comment { get { return _comment; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _comment = value; - return _comment; - }, _comment, CommentSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } } } diff --git a/src/Umbraco.Core/Models/TaskType.cs b/src/Umbraco.Core/Models/TaskType.cs index 3f753f5ad9..5a1e21e661 100644 --- a/src/Umbraco.Core/Models/TaskType.cs +++ b/src/Umbraco.Core/Models/TaskType.cs @@ -19,7 +19,12 @@ namespace Umbraco.Core.Models _alias = alias; } - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + } /// /// Gets or sets the Alias of the TaskType @@ -28,14 +33,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index cfc7cb1407..e0ceabfd17 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -26,10 +26,15 @@ namespace Umbraco.Core.Models private string _masterTemplateAlias; private Lazy _masterTemplateId; - private static readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); - private static readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.MasterTemplateId); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); + public readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.MasterTemplateId); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + } public Template(string name, string alias) : this(name, alias, (Func) null) @@ -47,57 +52,27 @@ namespace Umbraco.Core.Models public Lazy MasterTemplateId { get { return _masterTemplateId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _masterTemplateId = value; - return _masterTemplateId; - }, _masterTemplateId, MasterTemplateIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, Ps.Value.MasterTemplateIdSelector); } } public string MasterTemplateAlias { get { return _masterTemplateAlias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _masterTemplateAlias = value; - return _masterTemplateAlias; - }, _masterTemplateAlias, MasterTemplateAliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, Ps.Value.MasterTemplateAliasSelector); } } [DataMember] public new string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } [DataMember] public new string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value.ToCleanString(CleanStringType.UnderscoreAlias); - return _alias; - }, _alias, AliasSelector); - - } + set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.UnderscoreAlias), ref _alias, Ps.Value.AliasSelector); } } /// diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index 943f96c9f9..8a3ef94d5b 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -24,51 +24,34 @@ namespace Umbraco.Core.Models private int? _languageId; private string _domainName; - private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId); - private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); - private static readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName); - + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId); + public readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); + public readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName); + } [DataMember] public int? LanguageId { get { return _languageId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _languageId = value; - return _languageId; - }, _languageId, DefaultLanguageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _languageId, Ps.Value.DefaultLanguageSelector); } } [DataMember] public string DomainName { get { return _domainName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _domainName = value; - return _domainName; - }, _domainName, DomainNameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _domainName, Ps.Value.DomainNameSelector); } } [DataMember] public int? RootContentId { get { return _contentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _contentId = value; - return _contentId; - }, _contentId, ContentSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _contentId, Ps.Value.ContentSelector); } } public bool IsWildcard diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 70ef056068..48752468d5 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -26,22 +26,28 @@ namespace Umbraco.Core.Models private bool _hasPendingChanges; private string _contentTypeAlias; private Guid _nodeObjectTypeId; + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo HasChildrenSelector = ExpressionHelper.GetPropertyInfo(x => x.HasChildren); + public readonly PropertyInfo IsPublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsPublished); + public readonly PropertyInfo IsDraftSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDraft); + public readonly PropertyInfo HasPendingChangesSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPendingChanges); + public readonly PropertyInfo ContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); + public readonly PropertyInfo ContentTypeIconSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeIcon); + public readonly PropertyInfo ContentTypeThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeThumbnail); + public readonly PropertyInfo NodeObjectTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeObjectTypeId); + } - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo HasChildrenSelector = ExpressionHelper.GetPropertyInfo(x => x.HasChildren); - private static readonly PropertyInfo IsPublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsPublished); - private static readonly PropertyInfo IsDraftSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDraft); - private static readonly PropertyInfo HasPendingChangesSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPendingChanges); - private static readonly PropertyInfo ContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); - private static readonly PropertyInfo ContentTypeIconSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeIcon); - private static readonly PropertyInfo ContentTypeThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeThumbnail); - private static readonly PropertyInfo NodeObjectTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeObjectTypeId); private string _contentTypeIcon; private string _contentTypeThumbnail; @@ -66,92 +72,43 @@ namespace Umbraco.Core.Models public int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } public int Level { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } public int ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } public string Path { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } public bool Trashed { get { return _trashed; } - private set - { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); - } + private set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } public IDictionary AdditionalData { get; private set; } @@ -162,12 +119,7 @@ namespace Umbraco.Core.Models get { return _hasChildren; } set { - SetPropertyValueAndDetectChanges(o => - { - _hasChildren = value; - return _hasChildren; - }, _hasChildren, HasChildrenSelector); - + SetPropertyValueAndDetectChanges(value, ref _hasChildren, Ps.Value.HasChildrenSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["HasChildren"] = value; } @@ -178,12 +130,7 @@ namespace Umbraco.Core.Models get { return _isPublished; } set { - SetPropertyValueAndDetectChanges(o => - { - _isPublished = value; - return _isPublished; - }, _isPublished, IsPublishedSelector); - + SetPropertyValueAndDetectChanges(value, ref _isPublished, Ps.Value.IsPublishedSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["IsPublished"] = value; } @@ -194,12 +141,7 @@ namespace Umbraco.Core.Models get { return _isDraft; } set { - SetPropertyValueAndDetectChanges(o => - { - _isDraft = value; - return _isDraft; - }, _isDraft, IsDraftSelector); - + SetPropertyValueAndDetectChanges(value, ref _isDraft, Ps.Value.IsDraftSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["IsDraft"] = value; } @@ -210,12 +152,7 @@ namespace Umbraco.Core.Models get { return _hasPendingChanges; } set { - SetPropertyValueAndDetectChanges(o => - { - _hasPendingChanges = value; - return _hasPendingChanges; - }, _hasPendingChanges, HasPendingChangesSelector); - + SetPropertyValueAndDetectChanges(value, ref _hasPendingChanges, Ps.Value.HasPendingChangesSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["HasPendingChanges"] = value; } @@ -226,12 +163,7 @@ namespace Umbraco.Core.Models get { return _contentTypeAlias; } set { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeAlias = value; - return _contentTypeAlias; - }, _contentTypeAlias, ContentTypeAliasSelector); - + SetPropertyValueAndDetectChanges(value, ref _contentTypeAlias, Ps.Value.ContentTypeAliasSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["ContentTypeAlias"] = value; } @@ -242,12 +174,7 @@ namespace Umbraco.Core.Models get { return _contentTypeIcon; } set { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeIcon = value; - return _contentTypeIcon; - }, _contentTypeIcon, ContentTypeIconSelector); - + SetPropertyValueAndDetectChanges(value, ref _contentTypeIcon, Ps.Value.ContentTypeIconSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["ContentTypeIcon"] = value; } @@ -258,12 +185,7 @@ namespace Umbraco.Core.Models get { return _contentTypeThumbnail; } set { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeThumbnail = value; - return _contentTypeThumbnail; - }, _contentTypeThumbnail, ContentTypeThumbnailSelector); - + SetPropertyValueAndDetectChanges(value, ref _contentTypeThumbnail, Ps.Value.ContentTypeThumbnailSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["ContentTypeThumbnail"] = value; } @@ -274,12 +196,7 @@ namespace Umbraco.Core.Models get { return _nodeObjectTypeId; } set { - SetPropertyValueAndDetectChanges(o => - { - _nodeObjectTypeId = value; - return _nodeObjectTypeId; - }, _nodeObjectTypeId, NodeObjectTypeIdSelector); - + SetPropertyValueAndDetectChanges(value, ref _nodeObjectTypeId, Ps.Value.NodeObjectTypeIdSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["NodeObjectTypeId"] = value; } diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 620eb7eccb..6d1a2c5149 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -10,15 +10,28 @@ using Umbraco.Core.Plugins; namespace Umbraco.Core { + /// + /// Provides object extension methods. + /// public static class ObjectExtensions { //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); + /// + /// + /// + /// + /// + /// public static IEnumerable AsEnumerableOfOne(this T input) { return Enumerable.Repeat(input, 1); } + /// + /// + /// + /// public static void DisposeIfDisposable(this object input) { var disposable = input as IDisposable; @@ -26,7 +39,8 @@ namespace Umbraco.Core } /// - /// Provides a shortcut way of safely casting an input when you cannot guarantee the is an instance type (i.e., when the C# AS keyword is not applicable) + /// Provides a shortcut way of safely casting an input when you cannot guarantee the is + /// an instance type (i.e., when the C# AS keyword is not applicable). /// /// /// The input. @@ -64,30 +78,30 @@ namespace Umbraco.Core } /// - /// Tries to convert the input object to the output type using TypeConverters. If the destination type is a superclass of the input type, - /// if will use . + /// Tries to convert the input object to the output type using TypeConverters. If the destination + /// type is a superclass of the input type, if will use . /// /// The input. /// Type of the destination. /// public static Attempt TryConvertTo(this object input, Type destinationType) { - //if it is null and it is nullable, then return success with null - if (input == null && destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof (Nullable<>)) - { - return Attempt.Succeed(null); + // if null... + if (input == null) + { + // nullable is ok + if (destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>)) + return Attempt.Succeed(null); + + // value type is nok, else can be null, so is ok + return Attempt.SucceedIf(destinationType.IsValueType == false, null); } - - //if its not nullable and it is a value type - if (input == null && destinationType.IsValueType) return Attempt.Fail(); - //if the type can be null, then no problem - if (input == null && destinationType.IsValueType == false) return Attempt.Succeed(null); + // easy if (destinationType == typeof(object)) return Attempt.Succeed(input); - if (input.GetType() == destinationType) return Attempt.Succeed(input); - //check for string so that overloaders of ToString() can take advantage of the conversion. + // check for string so that overloaders of ToString() can take advantage of the conversion. if (destinationType == typeof(string)) return Attempt.Succeed(input.ToString()); // if we've got a nullable of something, we try to convert directly to that thing. @@ -117,9 +131,10 @@ namespace Umbraco.Core { if (input is string) { + // try convert from string, returns an Attempt if the string could be + // processed (either succeeded or failed), else null if we need to try + // other methods var result = TryConvertToFromString(input as string, destinationType); - - // if we processed the string (succeed or fail), we're done if (result.HasValue) return result.Value; } @@ -202,90 +217,124 @@ namespace Umbraco.Core return Attempt.Fail(); } + // returns an attempt if the string has been processed (either succeeded or failed) + // returns null if we need to try other methods private static Attempt? TryConvertToFromString(this string input, Type destinationType) { + // easy if (destinationType == typeof(string)) return Attempt.Succeed(input); - if (string.IsNullOrEmpty(input)) + // null, empty, whitespaces + if (string.IsNullOrWhiteSpace(input)) { - if (destinationType == typeof(Boolean)) - return Attempt.Succeed(false); // special case for booleans, null/empty == false - if (destinationType == typeof(DateTime)) + if (destinationType == typeof(bool)) // null/empty = bool false + return Attempt.Succeed(false); + if (destinationType == typeof(DateTime)) // null/empty = min DateTime value return Attempt.Succeed(DateTime.MinValue); + + // cannot decide here, + // any of the types below will fail parsing and will return a failed attempt + // but anything else will not be processed and will return null + // so even though the string is null/empty we have to proceed } - // we have a non-empty string, look for type conversions in the expected order of frequency of use... + // look for type conversions in the expected order of frequency of use... if (destinationType.IsPrimitive) { - if (destinationType == typeof(Int32)) + if (destinationType == typeof(int)) // aka Int32 { - Int32 value; - return Int32.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - if (destinationType == typeof(Int64)) - { - Int64 value; - return Int64.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - if (destinationType == typeof(Boolean)) - { - Boolean value; - if (Boolean.TryParse(input, out value)) - return Attempt.Succeed(value); // don't declare failure so the CustomBooleanTypeConverter can try - } - else if (destinationType == typeof(Int16)) - { - Int16 value; - return Int16.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - else if (destinationType == typeof(Double)) - { - Double value; - var input2 = NormalizeNumberDecimalSeparator(input); - return Double.TryParse(input2, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - else if (destinationType == typeof(Single)) - { - Single value; + int value; + if (int.TryParse(input, out value)) return Attempt.Succeed(value); + + // because decimal 100.01m will happily convert to integer 100, it + // makes sense that string "100.01" *also* converts to integer 100. + decimal value2; var input2 = NormalizeNumberDecimalSeparator(input); - return Single.TryParse(input2, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - else if (destinationType == typeof(Char)) + return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt32(value2)); + } + + if (destinationType == typeof(long)) // aka Int64 { - Char value; - return Char.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + long value; + if (long.TryParse(input, out value)) return Attempt.Succeed(value); + + // same as int + decimal value2; + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt64(value2)); } - else if (destinationType == typeof(Byte)) + + // fixme - should we do the decimal trick for short, byte, unsigned? + + if (destinationType == typeof(bool)) // aka Boolean { - Byte value; - return Byte.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + bool value; + if (bool.TryParse(input, out value)) return Attempt.Succeed(value); + // don't declare failure so the CustomBooleanTypeConverter can try + return null; } - else if (destinationType == typeof(SByte)) + + if (destinationType == typeof(short)) // aka Int16 { - SByte value; - return SByte.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + short value; + return Attempt.SucceedIf(short.TryParse(input, out value), value); } - else if (destinationType == typeof(UInt32)) + + if (destinationType == typeof(double)) // aka Double { - UInt32 value; - return UInt32.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + double value; + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(double.TryParse(input2, out value), value); } - else if (destinationType == typeof(UInt16)) + + if (destinationType == typeof(float)) // aka Single { - UInt16 value; - return UInt16.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + float value; + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(float.TryParse(input2, out value), value); } - else if (destinationType == typeof(UInt64)) + + if (destinationType == typeof(char)) // aka Char { - UInt64 value; - return UInt64.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + char value; + return Attempt.SucceedIf(char.TryParse(input, out value), value); + } + + if (destinationType == typeof(byte)) // aka Byte + { + byte value; + return Attempt.SucceedIf(byte.TryParse(input, out value), value); + } + + if (destinationType == typeof(sbyte)) // aka SByte + { + sbyte value; + return Attempt.SucceedIf(sbyte.TryParse(input, out value), value); + } + + if (destinationType == typeof(uint)) // aka UInt32 + { + uint value; + return Attempt.SucceedIf(uint.TryParse(input, out value), value); + } + + if (destinationType == typeof(ushort)) // aka UInt16 + { + ushort value; + return Attempt.SucceedIf(ushort.TryParse(input, out value), value); + } + + if (destinationType == typeof(ulong)) // aka UInt64 + { + ulong value; + return Attempt.SucceedIf(ulong.TryParse(input, out value), value); } } else if (destinationType == typeof(Guid)) { Guid value; - return Guid.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(Guid.TryParse(input, out value), value); } else if (destinationType == typeof(DateTime)) { @@ -308,30 +357,30 @@ namespace Umbraco.Core else if (destinationType == typeof(DateTimeOffset)) { DateTimeOffset value; - return DateTimeOffset.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out value), value); } else if (destinationType == typeof(TimeSpan)) { TimeSpan value; - return TimeSpan.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(TimeSpan.TryParse(input, out value), value); } - else if (destinationType == typeof(Decimal)) + else if (destinationType == typeof(decimal)) // aka Decimal { - Decimal value; + decimal value; var input2 = NormalizeNumberDecimalSeparator(input); - return Decimal.TryParse(input2, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(decimal.TryParse(input2, out value), value); } else if (destinationType == typeof(Version)) { Version value; - return Version.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(Version.TryParse(input, out value), value); } // E_NOTIMPL IPAddress, BigInteger return null; // we can't decide... } - private static readonly char[] NumberDecimalSeparatorsToNormalize = new[] {'.', ','}; + private static readonly char[] NumberDecimalSeparatorsToNormalize = {'.', ','}; private static string NormalizeNumberDecimalSeparator(string s) { diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 89ac5b779b..bf3215cce5 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -107,6 +107,13 @@ namespace Umbraco.Core.Persistence if (settings == null) return; // not configured + // could as well be + // so need to test the values too + var connectionString = settings.ConnectionString; + var providerName = settings.ProviderName; + if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName)) + return; // not configured + Configure(settings.ConnectionString, settings.ProviderName); } @@ -148,8 +155,8 @@ namespace Umbraco.Core.Persistence if (Configured) throw new InvalidOperationException("Already configured."); - Mandate.ParameterNotNullOrEmpty(connectionString, nameof(connectionString)); - Mandate.ParameterNotNullOrEmpty(providerName, nameof(providerName)); + if (connectionString.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(connectionString)); + if (providerName.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(providerName)); _connectionString = connectionString; _providerName = providerName; @@ -253,5 +260,14 @@ namespace Umbraco.Core.Persistence db?.Dispose(); Configured = false; } + + // during tests, the thread static var can leak between tests + // this method provides a way to force-reset the variable + internal void ResetForTests() + { + var db = _scopeContextAdapter.Get(HttpItemKey) as UmbracoDatabase; + _scopeContextAdapter.Clear(HttpItemKey); + db?.Dispose(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 49445ffc9d..43f77a8849 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -29,31 +29,40 @@ namespace Umbraco.Core.Persistence.Factories public IContent BuildEntity(DocumentDto dto) { - var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType) + var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType); + + try { - Id = _id, - Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId, - Name = dto.Text, - NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text, - Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, - CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value, - WriterId = dto.WriterUserId, - Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, - ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId, - SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder, - Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed, - Published = dto.Published, - CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - UpdateDate = dto.ContentVersionDto.VersionDate, - ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null, - ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null, - Version = dto.ContentVersionDto.VersionId, - PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - content.ResetDirtyProperties(false); - return content; + content.DisableChangeTracking(); + + content.Id = _id; + content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; + content.Name = dto.Text; + content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text; + content.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; + content.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; + content.WriterId = dto.WriterUserId; + content.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; + content.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; + content.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; + content.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; + content.Published = dto.Published; + content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; + content.UpdateDate = dto.ContentVersionDto.VersionDate; + content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?) null; + content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?) null; + content.Version = dto.ContentVersionDto.VersionId; + content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + content.ResetDirtyProperties(false); + return content; + } + finally + { + content.EnableChangeTracking(); + } } public DocumentDto BuildDto(IContent entity) diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 54c7d8d2c9..6fa13654ad 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -21,13 +21,22 @@ namespace Umbraco.Core.Persistence.Factories public IContentType BuildContentTypeEntity(ContentTypeDto dto) { var contentType = new ContentType(dto.NodeDto.ParentId); - BuildCommonEntity(contentType, dto); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - contentType.ResetDirtyProperties(false); + try + { + contentType.DisableChangeTracking(); - return contentType; + BuildCommonEntity(contentType, dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + contentType.ResetDirtyProperties(false); + return contentType; + } + finally + { + contentType.EnableChangeTracking(); + } } #endregion @@ -37,11 +46,20 @@ namespace Umbraco.Core.Persistence.Factories public IMediaType BuildMediaTypeEntity(ContentTypeDto dto) { var contentType = new MediaType(dto.NodeDto.ParentId); - BuildCommonEntity(contentType, dto); + try + { + contentType.DisableChangeTracking(); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - contentType.ResetDirtyProperties(false); + BuildCommonEntity(contentType, dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + contentType.ResetDirtyProperties(false); + } + finally + { + contentType.EnableChangeTracking(); + } return contentType; } diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index 377d70e3cb..cf487fa1a2 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -19,25 +19,35 @@ namespace Umbraco.Core.Persistence.Factories public IDataTypeDefinition BuildEntity(DataTypeDto dto) { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias) - { - CreateDate = dto.NodeDto.CreateDate, - DatabaseType = dto.DbType.EnumParse(true), - Id = dto.DataTypeId, - Key = dto.NodeDto.UniqueId, - Level = dto.NodeDto.Level, - UpdateDate = dto.NodeDto.CreateDate, - Name = dto.NodeDto.Text, - ParentId = dto.NodeDto.ParentId, - Path = dto.NodeDto.Path, - SortOrder = dto.NodeDto.SortOrder, - Trashed = dto.NodeDto.Trashed, - CreatorId = dto.NodeDto.UserId.Value - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - dataTypeDefinition.ResetDirtyProperties(false); - return dataTypeDefinition; + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); + + + try + { + dataTypeDefinition.DisableChangeTracking(); + + dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); + dataTypeDefinition.Id = dto.DataTypeId; + dataTypeDefinition.Key = dto.NodeDto.UniqueId; + dataTypeDefinition.Level = dto.NodeDto.Level; + dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.Name = dto.NodeDto.Text; + dataTypeDefinition.ParentId = dto.NodeDto.ParentId; + dataTypeDefinition.Path = dto.NodeDto.Path; + dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; + dataTypeDefinition.Trashed = dto.NodeDto.Trashed; + dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + dataTypeDefinition.ResetDirtyProperties(false); + return dataTypeDefinition; + } + finally + { + dataTypeDefinition.EnableChangeTracking(); + } } public DataTypeDto BuildDto(IDataTypeDefinition entity) diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs index 1b9d73bdd4..4bcfea58ca 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs @@ -10,15 +10,23 @@ namespace Umbraco.Core.Persistence.Factories public IDictionaryItem BuildEntity(DictionaryDto dto) { - var item = new DictionaryItem(dto.Parent, dto.Key) - { - Id = dto.PrimaryKey, - Key = dto.UniqueId - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - item.ResetDirtyProperties(false); - return item; + var item = new DictionaryItem(dto.Parent, dto.Key); + + try + { + item.DisableChangeTracking(); + + item.Id = dto.PrimaryKey; + item.Key = dto.UniqueId; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + item.ResetDirtyProperties(false); + return item; + } + finally + { + item.EnableChangeTracking(); + } } public DictionaryDto BuildDto(IDictionaryItem entity) diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs index 65297b9529..2a931f6069 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs @@ -17,13 +17,23 @@ namespace Umbraco.Core.Persistence.Factories public IDictionaryTranslation BuildEntity(LanguageTextDto dto) { - var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId) - {Id = dto.PrimaryKey}; + var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - item.ResetDirtyProperties(false); - return item; + try + { + item.DisableChangeTracking(); + + item.Id = dto.PrimaryKey; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + item.ResetDirtyProperties(false); + return item; + } + finally + { + item.EnableChangeTracking(); + } } public LanguageTextDto BuildDto(IDictionaryTranslation entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs index 6327a2ac62..237bf1c521 100644 --- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs @@ -12,15 +12,26 @@ namespace Umbraco.Core.Persistence.Factories public IMacro BuildEntity(MacroDto dto) { var model = new Macro(dto.Id, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.MacroFilePath); - foreach (var p in dto.MacroPropertyDtos.EmptyNull()) + + + try { - model.Properties.Add(new MacroProperty(p.Id, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); + model.DisableChangeTracking(); + + foreach (var p in dto.MacroPropertyDtos.EmptyNull()) + { + model.Properties.Add(new MacroProperty(p.Id, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); + } + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + model.ResetDirtyProperties(false); + return model; + } + finally + { + model.EnableChangeTracking(); } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - model.ResetDirtyProperties(false); - return model; } public MacroDto BuildDto(IMacro entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs index 2d8edcac52..0fcb654cb7 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs @@ -29,24 +29,32 @@ namespace Umbraco.Core.Persistence.Factories public IMedia BuildEntity(ContentVersionDto dto) { - var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType) - { - Id = _id, - Key = dto.ContentDto.NodeDto.UniqueId, - Path = dto.ContentDto.NodeDto.Path, - CreatorId = dto.ContentDto.NodeDto.UserId.Value, - Level = dto.ContentDto.NodeDto.Level, - ParentId = dto.ContentDto.NodeDto.ParentId, - SortOrder = dto.ContentDto.NodeDto.SortOrder, - Trashed = dto.ContentDto.NodeDto.Trashed, - CreateDate = dto.ContentDto.NodeDto.CreateDate, - UpdateDate = dto.VersionDate, - Version = dto.VersionId - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - media.ResetDirtyProperties(false); - return media; + var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType); + + try + { + media.DisableChangeTracking(); + + media.Id = _id; + media.Key = dto.ContentDto.NodeDto.UniqueId; + media.Path = dto.ContentDto.NodeDto.Path; + media.CreatorId = dto.ContentDto.NodeDto.UserId.Value; + media.Level = dto.ContentDto.NodeDto.Level; + media.ParentId = dto.ContentDto.NodeDto.ParentId; + media.SortOrder = dto.ContentDto.NodeDto.SortOrder; + media.Trashed = dto.ContentDto.NodeDto.Trashed; + media.CreateDate = dto.ContentDto.NodeDto.CreateDate; + media.UpdateDate = dto.VersionDate; + media.Version = dto.VersionId; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + media.ResetDirtyProperties(false); + return media; + } + finally + { + media.EnableChangeTracking(); + } } public ContentVersionDto BuildDto(IMedia entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs index a35c472f24..2901f48539 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs @@ -31,26 +31,35 @@ namespace Umbraco.Core.Persistence.Factories public IMember BuildEntity(MemberDto dto) { var member = new Member( - dto.ContentVersionDto.ContentDto.NodeDto.Text, - dto.Email,dto.LoginName,dto.Password, _contentType) + dto.ContentVersionDto.ContentDto.NodeDto.Text, + dto.Email, dto.LoginName, dto.Password, _contentType); + + try { - Id = _id, - Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId, - Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, - CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value, - Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, - ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId, - SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder, - Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed, - CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - UpdateDate = dto.ContentVersionDto.VersionDate, - Version = dto.ContentVersionDto.VersionId - }; - member.ProviderUserKey = member.Key; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - member.ResetDirtyProperties(false); - return member; + member.DisableChangeTracking(); + + member.Id = _id; + member.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; + member.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; + member.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; + member.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; + member.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; + member.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; + member.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; + member.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; + member.UpdateDate = dto.ContentVersionDto.VersionDate; + member.Version = dto.ContentVersionDto.VersionId; + + member.ProviderUserKey = member.Key; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + member.ResetDirtyProperties(false); + return member; + } + finally + { + member.EnableChangeTracking(); + } } public MemberDto BuildDto(IMember entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs index 9544d170e2..a4e069e85c 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs @@ -18,18 +18,26 @@ namespace Umbraco.Core.Persistence.Factories public IMemberGroup BuildEntity(NodeDto dto) { - var template = new MemberGroup + var group = new MemberGroup(); + + try { - CreateDate = dto.CreateDate, - Id = dto.NodeId, - Key = dto.UniqueId, - Name = dto.Text - }; + group.DisableChangeTracking(); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - template.ResetDirtyProperties(false); - return template; + group.CreateDate = dto.CreateDate; + group.Id = dto.NodeId; + group.Key = dto.UniqueId; + group.Name = dto.Text; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + group.ResetDirtyProperties(false); + return group; + } + finally + { + group.EnableChangeTracking(); + } } public NodeDto BuildDto(IMemberGroup entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index eebbc34eda..5e34fb8936 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -12,48 +12,56 @@ namespace Umbraco.Core.Persistence.Factories public IMemberType BuildEntity(MemberTypeReadOnlyDto dto) { var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); - - var memberType = new MemberType(dto.ParentId) - { - Alias = dto.Alias, - AllowedAsRoot = dto.AllowAtRoot, - CreateDate = dto.CreateDate, - CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0, - Description = dto.Description, - Icon = dto.Icon, - Id = dto.NodeId, - IsContainer = dto.IsContainer, - Key = dto.UniqueId.Value, - Level = dto.Level, - Name = dto.Text, - Path = dto.Path, - SortOrder = dto.SortOrder, - Thumbnail = dto.Thumbnail, - Trashed = dto.Trashed, - UpdateDate = dto.CreateDate, - AllowedContentTypes = Enumerable.Empty() - }; - var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); - memberType.PropertyGroups = propertyTypeGroupCollection; + var memberType = new MemberType(dto.ParentId); - var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); - - //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. - foreach (var standardPropertyType in standardPropertyTypes) + try { - if(dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; - - //Add the standard PropertyType to the current list - propertyTypes.Add(standardPropertyType.Value); + memberType.DisableChangeTracking(); - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, - new MemberTypePropertyProfileAccess(false, false)); + memberType.Alias = dto.Alias; + memberType.AllowedAsRoot = dto.AllowAtRoot; + memberType.CreateDate = dto.CreateDate; + memberType.CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0; + memberType.Description = dto.Description; + memberType.Icon = dto.Icon; + memberType.Id = dto.NodeId; + memberType.IsContainer = dto.IsContainer; + memberType.Key = dto.UniqueId.Value; + memberType.Level = dto.Level; + memberType.Name = dto.Text; + memberType.Path = dto.Path; + memberType.SortOrder = dto.SortOrder; + memberType.Thumbnail = dto.Thumbnail; + memberType.Trashed = dto.Trashed; + memberType.UpdateDate = dto.CreateDate; + memberType.AllowedContentTypes = Enumerable.Empty(); + + var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); + memberType.PropertyGroups = propertyTypeGroupCollection; + + var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); + + //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. + foreach (var standardPropertyType in standardPropertyTypes) + { + if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; + + //Add the standard PropertyType to the current list + propertyTypes.Add(standardPropertyType.Value); + + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, + new MemberTypePropertyProfileAccess(false, false)); + } + memberType.NoGroupPropertyTypes = propertyTypes; + + return memberType; + } + finally + { + memberType.EnableChangeTracking(); } - memberType.NoGroupPropertyTypes = propertyTypes; - - return memberType; } private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) @@ -75,6 +83,7 @@ namespace Umbraco.Core.Persistence.Factories group.Id = groupDto.Id.Value; } + group.Key = groupDto.UniqueId; group.Name = groupDto.Text; group.SortOrder = groupDto.SortOrder; group.PropertyTypes = new PropertyTypeCollection(); @@ -114,7 +123,8 @@ namespace Umbraco.Core.Persistence.Factories ValidationRegExp = typeDto.ValidationRegExp, PropertyGroupId = new Lazy(() => tempGroupDto.Id.Value), CreateDate = memberType.CreateDate, - UpdateDate = memberType.UpdateDate + UpdateDate = memberType.UpdateDate, + Key = typeDto.UniqueId }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -166,7 +176,8 @@ namespace Umbraco.Core.Persistence.Factories ValidationRegExp = typeDto.ValidationRegExp, PropertyGroupId = null, CreateDate = dto.CreateDate, - UpdateDate = dto.CreateDate + UpdateDate = dto.CreateDate, + Key = typeDto.UniqueId }; propertyTypes.Add(propertyType); diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 8d51b627ea..446bd426ad 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -42,12 +42,22 @@ namespace Umbraco.Core.Persistence.Factories : propertyType.CreatePropertyFromRawValue(propertyDataDto.GetValue, propertyDataDto.VersionId.Value, propertyDataDto.Id); - //on initial construction we don't want to have dirty properties tracked - property.CreateDate = _createDate; - property.UpdateDate = _updateDate; - // http://issues.umbraco.org/issue/U4-1946 - property.ResetDirtyProperties(false); - properties.Add(property); + try + { + //on initial construction we don't want to have dirty properties tracked + property.DisableChangeTracking(); + + property.CreateDate = _createDate; + property.UpdateDate = _updateDate; + // http://issues.umbraco.org/issue/U4-1946 + property.ResetDirtyProperties(false); + properties.Add(property); + } + finally + { + property.EnableChangeTracking(); + } + } return properties; @@ -89,7 +99,7 @@ namespace Umbraco.Core.Persistence.Factories decimal val; if (decimal.TryParse(property.Value.ToString(), out val)) { - dto.Decimal = val; + dto.Decimal = val; // property value should be normalized already } } else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date && property.Value != null && string.IsNullOrWhiteSpace(property.Value.ToString()) == false) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index 2dfb996bb3..e1db8dcebf 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -39,47 +39,65 @@ namespace Umbraco.Core.Persistence.Factories { var group = new PropertyGroup(); - // if the group is defined on the current content type, - // assign its identifier, else it will be zero - if (groupDto.ContentTypeNodeId == _contentTypeId) - group.Id = groupDto.Id; - - group.Name = groupDto.Text; - group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(); - group.Key = groupDto.UniqueId; - - //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded - var typeDtos = groupDto.PropertyTypeDtos.Where(x => x.Id > 0); - foreach (var typeDto in typeDtos) + try { - var tempGroupDto = groupDto; - var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.PropertyEditorAlias, - typeDto.DataTypeDto.DbType.EnumParse(true), - typeDto.Alias); + group.DisableChangeTracking(); - propertyType.Alias = typeDto.Alias; - propertyType.DataTypeDefinitionId = typeDto.DataTypeId; - propertyType.Description = typeDto.Description; - propertyType.Id = typeDto.Id; - propertyType.Key = typeDto.UniqueId; - propertyType.Name = typeDto.Name; - propertyType.Mandatory = typeDto.Mandatory; - propertyType.SortOrder = typeDto.SortOrder; - propertyType.ValidationRegExp = typeDto.ValidationRegExp; - propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); - propertyType.CreateDate = _createDate; - propertyType.UpdateDate = _updateDate; + // if the group is defined on the current content type, + // assign its identifier, else it will be zero + if (groupDto.ContentTypeNodeId == _contentTypeId) + group.Id = groupDto.Id; + group.Name = groupDto.Text; + group.SortOrder = groupDto.SortOrder; + group.PropertyTypes = new PropertyTypeCollection(); + group.Key = groupDto.UniqueId; + + //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded + var typeDtos = groupDto.PropertyTypeDtos.Where(x => x.Id > 0); + foreach (var typeDto in typeDtos) + { + var tempGroupDto = groupDto; + var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.PropertyEditorAlias, + typeDto.DataTypeDto.DbType.EnumParse(true), + typeDto.Alias); + + try + { + propertyType.DisableChangeTracking(); + + propertyType.Alias = typeDto.Alias; + propertyType.DataTypeDefinitionId = typeDto.DataTypeId; + propertyType.Description = typeDto.Description; + propertyType.Id = typeDto.Id; + propertyType.Key = typeDto.UniqueId; + propertyType.Name = typeDto.Name; + propertyType.Mandatory = typeDto.Mandatory; + propertyType.SortOrder = typeDto.SortOrder; + propertyType.ValidationRegExp = typeDto.ValidationRegExp; + propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); + propertyType.CreateDate = _createDate; + propertyType.UpdateDate = _updateDate; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + propertyType.ResetDirtyProperties(false); + group.PropertyTypes.Add(propertyType); + } + finally + { + propertyType.EnableChangeTracking(); + } + } //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - propertyType.ResetDirtyProperties(false); - group.PropertyTypes.Add(propertyType); + group.ResetDirtyProperties(false); + propertyGroups.Add(group); + } + finally + { + group.EnableChangeTracking(); } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - group.ResetDirtyProperties(false); - propertyGroups.Add(group); } return propertyGroups; diff --git a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs index 5d614fb6d3..47adf75d89 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs @@ -16,17 +16,26 @@ namespace Umbraco.Core.Persistence.Factories public IRelation BuildEntity(RelationDto dto) { - var entity = new Relation(dto.ParentId, dto.ChildId, _relationType) + var entity = new Relation(dto.ParentId, dto.ChildId, _relationType); + + try { - Comment = dto.Comment, - CreateDate = dto.Datetime, - Id = dto.Id, - UpdateDate = dto.Datetime - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - return entity; + entity.DisableChangeTracking(); + + entity.Comment = dto.Comment; + entity.CreateDate = dto.Datetime; + entity.Id = dto.Id; + entity.UpdateDate = dto.Datetime; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public RelationDto BuildDto(IRelation entity) diff --git a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs index 358b652d0b..98d4f30042 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs @@ -9,16 +9,25 @@ namespace Umbraco.Core.Persistence.Factories public IRelationType BuildEntity(RelationTypeDto dto) { - var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias) + var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias); + + try { - Id = dto.Id, - IsBidirectional = dto.Dual, - Name = dto.Name - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - return entity; + entity.DisableChangeTracking(); + + entity.Id = dto.Id; + entity.IsBidirectional = dto.Dual; + entity.Name = dto.Name; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public RelationTypeDto BuildDto(IRelationType entity) diff --git a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs index 81a6703324..d60403abea 100644 --- a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs @@ -12,20 +12,28 @@ namespace Umbraco.Core.Persistence.Factories { public Task BuildEntity(TaskDto dto) { - var entity = new Task(new TaskType(dto.TaskTypeDto.Alias) { Id = dto.TaskTypeDto.Id }) + var entity = new Task(new TaskType(dto.TaskTypeDto.Alias) { Id = dto.TaskTypeDto.Id }); + + try { - Closed = dto.Closed, - AssigneeUserId = dto.UserId, - Comment = dto.Comment, - CreateDate = dto.DateTime, - EntityId = dto.NodeId, - Id = dto.Id, - OwnerUserId = dto.ParentUserId, - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - return entity; + entity.DisableChangeTracking(); + + entity.Closed = dto.Closed; + entity.AssigneeUserId = dto.UserId; + entity.Comment = dto.Comment; + entity.CreateDate = dto.DateTime; + entity.EntityId = dto.NodeId; + entity.Id = dto.Id; + entity.OwnerUserId = dto.ParentUserId; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public TaskDto BuildDto(Task entity) diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index 60cde916b6..ecff14d99c 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -35,23 +35,31 @@ namespace Umbraco.Core.Persistence.Factories public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions, Func getFileContent) { - var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent) - { - CreateDate = dto.NodeDto.CreateDate, - Id = dto.NodeId, - Key = dto.NodeDto.UniqueId, - Path = dto.NodeDto.Path - }; + var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent); - template.IsMasterTemplate = childDefinitions.Any(x => x.ParentId == dto.NodeId); + try + { + template.DisableChangeTracking(); - if(dto.NodeDto.ParentId > 0) - template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); + template.CreateDate = dto.NodeDto.CreateDate; + template.Id = dto.NodeId; + template.Key = dto.NodeDto.UniqueId; + template.Path = dto.NodeDto.Path; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - template.ResetDirtyProperties(false); - return template; + template.IsMasterTemplate = childDefinitions.Any(x => x.ParentId == dto.NodeId); + + if (dto.NodeDto.ParentId > 0) + template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + template.ResetDirtyProperties(false); + return template; + } + finally + { + template.EnableChangeTracking(); + } } public TemplateDto BuildDto(Template entity) diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 0685432398..cf173dfa0c 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -29,44 +29,52 @@ namespace Umbraco.Core.Persistence.Factories { var asDictionary = (IDictionary)d; - var entity = new UmbracoEntity(d.trashed) - { - CreateDate = d.createDate, - CreatorId = d.nodeUser, - Id = d.id, - Key = d.uniqueID, - Level = d.level, - Name = d.text, - NodeObjectTypeId = d.nodeObjectType, - ParentId = d.parentID, - Path = d.path, - SortOrder = d.sortOrder, - HasChildren = d.children > 0, - ContentTypeAlias = asDictionary.ContainsKey("alias") ? (d.alias ?? string.Empty) : string.Empty, - ContentTypeIcon = asDictionary.ContainsKey("icon") ? (d.icon ?? string.Empty) : string.Empty, - ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty, - }; + var entity = new UmbracoEntity(d.trashed); - var publishedVersion = default(Guid); - //some content items don't have a published version - if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) + try { - Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); - } - var newestVersion = default(Guid); - if (asDictionary.ContainsKey("newestVersion") && d.newestVersion != null) - { - Guid.TryParse(d.newestVersion.ToString(), out newestVersion); - } + entity.DisableChangeTracking(); - entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); - entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); - entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; - - //Now we can assign the additional data! - AddAdditionalData(entity, asDictionary); - - return entity; + entity.CreateDate = d.createDate; + entity.CreatorId = d.nodeUser; + entity.Id = d.id; + entity.Key = d.uniqueID; + entity.Level = d.level; + entity.Name = d.text; + entity.NodeObjectTypeId = d.nodeObjectType; + entity.ParentId = d.parentID; + entity.Path = d.path; + entity.SortOrder = d.sortOrder; + entity.HasChildren = d.children > 0; + entity.ContentTypeAlias = asDictionary.ContainsKey("alias") ? (d.alias ?? string.Empty) : string.Empty; + entity.ContentTypeIcon = asDictionary.ContainsKey("icon") ? (d.icon ?? string.Empty) : string.Empty; + entity.ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty; + + var publishedVersion = default(Guid); + //some content items don't have a published version + if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) + { + Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); + } + var newestVersion = default(Guid); + if (asDictionary.ContainsKey("newestVersion") && d.newestVersion != null) + { + Guid.TryParse(d.newestVersion.ToString(), out newestVersion); + } + + entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); + entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); + entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; + + //Now we can assign the additional data! + AddAdditionalData(entity, asDictionary); + + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public UmbracoEntity BuildEntity(EntityRepository.UmbracoEntityDto dto) diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index bbc193d461..5f0784d934 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -21,36 +21,44 @@ namespace Umbraco.Core.Persistence.Factories public IUser BuildEntity(UserDto dto) { var guidId = dto.Id.ToGuid(); - var user = new User(_userType) - { - Id = dto.Id, - Key = guidId, - StartContentId = dto.ContentStartId, - StartMediaId = dto.MediaStartId.HasValue ? dto.MediaStartId.Value : -1, - RawPasswordValue = dto.Password, - Username = dto.Login, - Name = dto.UserName, - IsLockedOut = dto.NoConsole, - IsApproved = dto.Disabled == false, - Email = dto.Email, - Language = dto.UserLanguage, - SecurityStamp = dto.SecurityStampToken, - FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0, - LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue, - LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue, - LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue - }; + var user = new User(_userType); - foreach (var app in dto.User2AppDtos.EmptyNull()) + try { - user.AddAllowedSection(app.AppAlias); + user.DisableChangeTracking(); + + user.Id = dto.Id; + user.Key = guidId; + user.StartContentId = dto.ContentStartId; + user.StartMediaId = dto.MediaStartId.HasValue ? dto.MediaStartId.Value : -1; + user.RawPasswordValue = dto.Password; + user.Username = dto.Login; + user.Name = dto.UserName; + user.IsLockedOut = dto.NoConsole; + user.IsApproved = dto.Disabled == false; + user.Email = dto.Email; + user.Language = dto.UserLanguage; + user.SecurityStamp = dto.SecurityStampToken; + user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0; + user.LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue; + user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; + user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; + + foreach (var app in dto.User2AppDtos.EmptyNull()) + { + user.AddAllowedSection(app.AppAlias); + } + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + user.ResetDirtyProperties(false); + + return user; + } + finally + { + user.EnableChangeTracking(); } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - user.ResetDirtyProperties(false); - - return user; } public UserDto BuildDto(IUser entity) diff --git a/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs index b5718b7c3b..5f9cfac994 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs @@ -11,19 +11,27 @@ namespace Umbraco.Core.Persistence.Factories public IUserType BuildEntity(UserTypeDto dto) { - var userType = new UserType - { - Alias = dto.Alias, - Id = dto.Id, - Name = dto.Name, - Permissions = dto.DefaultPermissions.IsNullOrWhiteSpace() - ? Enumerable.Empty() - : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)) - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - userType.ResetDirtyProperties(false); - return userType; + var userType = new UserType(); + + try + { + userType.DisableChangeTracking(); + + userType.Alias = dto.Alias; + userType.Id = dto.Id; + userType.Name = dto.Name; + userType.Permissions = dto.DefaultPermissions.IsNullOrWhiteSpace() + ? Enumerable.Empty() + : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + userType.ResetDirtyProperties(false); + return userType; + } + finally + { + userType.EnableChangeTracking(); + } } public UserTypeDto BuildDto(IUserType entity) diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs b/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs index 4ea66b869f..b5340832b5 100644 --- a/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs @@ -7,6 +7,6 @@ namespace Umbraco.Core.Persistence.Migrations /// public interface IMigrationExpression { - string Process(Database database); + string Process(UmbracoDatabase database); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 31e3152f04..5f2c17cfb9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -43,7 +43,6 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {3, typeof (ContentDto)}, {4, typeof (ContentVersionDto)}, {5, typeof (DocumentDto)}, - {6, typeof (ContentTypeTemplateDto)}, {7, typeof (DataTypeDto)}, {8, typeof (DataTypePreValueDto)}, @@ -64,8 +63,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {23, typeof (PropertyDataDto)}, {24, typeof (RelationTypeDto)}, {25, typeof (RelationDto)}, - {26, typeof (StylesheetDto)}, - {27, typeof (StylesheetPropertyDto)}, + {28, typeof (TagDto)}, {29, typeof (TagRelationshipDto)}, {31, typeof (UserTypeDto)}, @@ -80,12 +78,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {40, typeof (ServerRegistrationDto)}, {41, typeof (AccessDto)}, {42, typeof (AccessRuleDto)}, - {43, typeof(CacheInstructionDto)}, + {43, typeof (CacheInstructionDto)}, {44, typeof (ExternalLoginDto)}, {45, typeof (MigrationDto)}, {46, typeof (UmbracoDeployChecksumDto)}, {47, typeof (UmbracoDeployDependencyDto)}, - {48, typeof (LockDto) } + {48, typeof (RedirectUrlDto) }, + {49, typeof (LockDto) } }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs new file mode 100644 index 0000000000..e53cc7b143 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Text; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Migrations.Syntax.Alter; +using Umbraco.Core.Persistence.Migrations.Syntax.Create; +using Umbraco.Core.Persistence.Migrations.Syntax.Delete; +using Umbraco.Core.Persistence.Migrations.Syntax.Execute; + +namespace Umbraco.Core.Persistence.Migrations +{ + internal class LocalMigrationContext : MigrationContext + { + public LocalMigrationContext(UmbracoDatabase database, ILogger logger) + : base(database, logger) + { } + + public IExecuteBuilder Execute => new ExecuteBuilder(this); + + public IDeleteBuilder Delete => new DeleteBuilder(this); + + public IAlterSyntaxBuilder Alter => new AlterSyntaxBuilder(this); + + public ICreateBuilder Create => new CreateBuilder(this); + + public string GetSql() + { + var sb = new StringBuilder(); + foreach (var sql in Expressions.Select(x => x.Process(Database))) + { + sb.Append(sql); + sb.AppendLine(); + sb.AppendLine("GO"); + } + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs index 838a2f7aba..b39402adba 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence.Migrations || SupportedDatabaseTypes.Any(x => CurrentDatabaseType.GetType().Inherits(x.GetType())); } - public virtual string Process(Database database) + public virtual string Process(UmbracoDatabase database) { return ToString(); } diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index d60b5adeb6..0bd4dc72f6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Persistence.Migrations _targetVersion = targetVersion; _productName = productName; //ensure this is null if there aren't any - _migrations = migrations.Length == 0 ? null : migrations; + _migrations = migrations == null || migrations.Length == 0 ? null : migrations; } /// diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs index 72250877db..93661c9ece 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using System; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index @@ -17,12 +18,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index return this; } + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] public void OnColumn(string columnName) { var column = new IndexColumnDefinition { Name = columnName }; Expression.Index.Columns.Add(column); } + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] public void OnColumns(params string[] columnNames) { foreach (string columnName in columnNames) diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs index f2f4280f23..fcf5038a86 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs @@ -1,8 +1,13 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index +using System; + +namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index { public interface IDeleteIndexOnColumnSyntax : IFluentSyntax { + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] void OnColumn(string columnName); + + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] void OnColumns(params string[] columnNames); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs index 7ae8f2e3d7..b9f80b0638 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute _context.Expressions.Add(expression); } - public void Code(Func codeStatement) + public void Code(Func codeStatement) { var expression = new ExecuteCodeStatementExpression(_context, _supportedDatabaseTypes) { CodeStatement = codeStatement }; _context.Expressions.Add(expression); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs index 81a40e24a1..aa4c69d914 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs @@ -9,9 +9,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions : base(context, supportedDatabaseTypes) { } - public virtual Func CodeStatement { get; set; } + public virtual Func CodeStatement { get; set; } - public override string Process(Database database) + public override string Process(UmbracoDatabase database) { if(CodeStatement != null) return CodeStatement(database); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs index 1d4c8e3f55..41dfe1ac3b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs @@ -6,6 +6,6 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute public interface IExecuteBuilder : IFluentSyntax { void Sql(string sqlStatement); - void Code(Func codeStatement); + void Code(Func codeStatement); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs index 6663979ee6..a1e9a7b72a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions public virtual string OldName { get; set; } public virtual string NewName { get; set; } - public override string Process(Database database) + public override string Process(UmbracoDatabase database) { if (CurrentDatabaseType.IsMySql()) { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable.cs new file mode 100644 index 0000000000..fcae11bfa5 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable.cs @@ -0,0 +1,45 @@ +using System.Linq; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight +{ + [Migration("8.0.0", 100, GlobalSettings.UmbracoMigrationName)] + public class AddRedirectUrlTable : MigrationBase + { + public AddRedirectUrlTable(IMigrationContext context) + : base(context) + { } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + + private string MigrationCode(UmbracoDatabase database) + { + // don't execute if the table is already there + var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); + if (tables.InvariantContains("umbracoRedirectUrl")) return null; + + var localContext = new LocalMigrationContext(database, Logger); + + localContext.Create.Table("umbracoRedirectUrl") + .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoRedirectUrl") + .WithColumn("contentId").AsInt32().NotNullable() + .WithColumn("createDateUtc").AsDateTime().NotNullable() + .WithColumn("url").AsString(2048).NotNullable(); + + localContext.Create.Index("IX_umbracoRedirectUrl") + .OnTable("umbracoRedirectUrl") + .OnColumn("url").Ascending() + .OnColumn("createDateUtc").Ascending() + .WithOptions().NonClustered(); + + return localContext.GetSql(); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable2.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable2.cs new file mode 100644 index 0000000000..37fb5c417d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable2.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight +{ + [Migration("8.0.0", 101, GlobalSettings.UmbracoMigrationName)] + public class AddRedirectUrlTable2 : MigrationBase + { + public AddRedirectUrlTable2(IMigrationContext context) + : base(context) + { } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + + private string MigrationCode(UmbracoDatabase database) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("contentKey"))) + return null; + + var localContext = new LocalMigrationContext(database, Logger); + + localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field + + localContext.Delete.Column("contentId").FromTable("umbracoRedirectUrl"); + + // SQL CE does not want to alter-add non-nullable columns ;-( + // but it's OK to create as nullable then alter, go figure + //localContext.Alter.Table("umbracoRedirectUrl") + // .AddColumn("contentKey").AsGuid().NotNullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AddColumn("contentKey").AsGuid().Nullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AlterColumn("contentKey").AsGuid().NotNullable(); + + localContext.Create.ForeignKey("FK_umbracoRedirectUrl") + .FromTable("umbracoRedirectUrl").ForeignColumn("contentKey") + .ToTable("umbracoNode").PrimaryColumn("uniqueID"); + + return localContext.GetSql(); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable3.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable3.cs new file mode 100644 index 0000000000..3d4aa05a2e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable3.cs @@ -0,0 +1,55 @@ +using System.Linq; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight +{ + [Migration("8.0.0", 102, GlobalSettings.UmbracoMigrationName)] + public class AddRedirectUrlTable3 : MigrationBase + { + public AddRedirectUrlTable3(IMigrationContext context) + : base(context) + { } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + private string MigrationCode(UmbracoDatabase database) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("hurl"))) + return null; + + var localContext = new LocalMigrationContext(database, Logger); + + localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field + + localContext.Delete.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl"); + + // SQL CE does not want to alter-add non-nullable columns ;-( + // but it's OK to create as nullable then alter, go figure + //localContext.Alter.Table("umbracoRedirectUrl") + // .AddColumn("urlHash").AsString(16).NotNullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AddColumn("hurl").AsString(16).Nullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AlterColumn("hurl").AsString(16).NotNullable(); + + localContext.Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") + .OnColumn("hurl") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("createDateUtc") + .Descending() + .WithOptions().NonClustered(); + + return localContext.GetSql(); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable4.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable4.cs new file mode 100644 index 0000000000..7bde77bdda --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddRedirectUrlTable4.cs @@ -0,0 +1,57 @@ +using System.Linq; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight +{ + [Migration("8.0.0", 103, GlobalSettings.UmbracoMigrationName)] + public class AddRedirectUrlTable4 : MigrationBase + { + public AddRedirectUrlTable4(IMigrationContext context) + : base(context) + { } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + private string MigrationCode(UmbracoDatabase database) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("urlHash"))) + return null; + + var localContext = new LocalMigrationContext(database, Logger); + + localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field + + localContext.Delete.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl"); + + localContext.Delete.Column("hurl").FromTable("umbracoRedirectUrl"); + + // SQL CE does not want to alter-add non-nullable columns ;-( + // but it's OK to create as nullable then alter, go figure + //localContext.Alter.Table("umbracoRedirectUrl") + // .AddColumn("urlHash").AsString(16).NotNullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AddColumn("urlHash").AsString(16).Nullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AlterColumn("urlHash").AsString(16).NotNullable(); + + localContext.Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") + .OnColumn("urlHash") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("createDateUtc") + .Descending() + .WithOptions().NonClustered(); + + return localContext.GetSql(); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs new file mode 100644 index 0000000000..e8ea34995b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero +{ + /// + /// This is here to re-remove these tables, we dropped them in 7.3 but new installs created them again so we're going to re-drop them + /// + [Migration("7.5.0", 1, GlobalSettings.UmbracoMigrationName)] + public class RemoveStylesheetDataAndTablesAgain : MigrationBase + { + public RemoveStylesheetDataAndTablesAgain(IMigrationContext context) + : base(context) + { + } + + public override void Up() + { + //Clear all stylesheet data if the tables exist + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("cmsStylesheetProperty")) + { + Delete.FromTable("cmsStylesheetProperty").AllRows(); + Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.StylesheetProperty) }); + + Delete.Table("cmsStylesheetProperty"); + } + if (tables.InvariantContains("cmsStylesheet")) + { + Delete.FromTable("cmsStylesheet").AllRows(); + Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.Stylesheet) }); + + Delete.Table("cmsStylesheet"); + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs new file mode 100644 index 0000000000..b968e8ea58 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero +{ + /// + /// See: http://issues.umbraco.org/issue/U4-8522 + /// + [Migration("7.5.0", 2, GlobalSettings.UmbracoMigrationName)] + public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase + { + public UpdateUniqueIndexOnCmsPropertyData(IMigrationContext context) + : base(context) + { + } + + public override void Up() + { + //Clear all stylesheet data if the tables exist + //tuple = tablename, indexname, columnname, unique + var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); + var found = indexes.FirstOrDefault( + x => x.Item1.InvariantEquals("cmsPropertyData") + && x.Item2.InvariantEquals("IX_cmsPropertyData_1") + //we're searching for the old index which is not unique + && x.Item4 == false); + + if (found != null) + { + //Check for MySQL + if (DatabaseType.IsMySql()) + { + //Use the special double nested sub query for MySQL since that is the only + //way delete sub queries works + var delPropQry = SqlSyntax.GetDeleteSubquery( + "cmsPropertyData", + "id", + Context.Database.Sql("SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL"), + WhereInType.NotIn); + Execute.Sql(delPropQry.SQL); + } + else + { + //NOTE: Even though the above will work for MSSQL, we are not going to execute the + // nested delete sub query logic since it will be slower and there could be a ton of property + // data here so needs to be as fast as possible. + Execute.Sql("DELETE FROM cmsPropertyData WHERE id NOT IN (SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL)"); + } + + //we need to re create this index + Delete.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData"); + Create.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData") + .OnColumn("contentNodeId").Ascending() + .OnColumn("versionId").Ascending() + .OnColumn("propertytypeid").Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique(); + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index ff85fff35f..7a4fab20f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -145,6 +145,7 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new List { + "DELETE FROM umbracoRedirectUrl WHERE contentKey IN (SELECT uniqueID FROM umbracoNode WHERE id = @Id)", "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", @@ -697,8 +698,8 @@ namespace Umbraco.Core.Persistence.Repositories if (filter != null) { - foreach (var filterClaus in filter.GetWhereClauses()) - filterSql.Append($"AND ({filterClaus.Item1})", filterClaus.Item2); + foreach (var filterClause in filter.GetWhereClauses()) + filterSql.Append($"AND ({filterClause.Item1})", filterClause.Item2); } return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs new file mode 100644 index 0000000000..82f2e0e516 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Defines the repository. + /// + public interface IRedirectUrlRepository : IRepositoryQueryable + { + /// + /// Gets a redirect url. + /// + /// The Umbraco redirect url route. + /// The content unique key. + /// + IRedirectUrl Get(string url, Guid contentKey); + + /// + /// Deletes a redirect url. + /// + /// The redirect url identifier. + void Delete(int id); + + /// + /// Deletes all redirect urls. + /// + void DeleteAll(); + + /// + /// Deletes all redirect urls for a given content. + /// + /// The content unique key. + void DeleteContentUrls(Guid contentKey); + + /// + /// Gets the most recent redirect url corresponding to an Umbraco redirect url route. + /// + /// The Umbraco redirect url route. + /// The most recent redirect url corresponding to the route. + IRedirectUrl GetMostRecentUrl(string url); + + /// + /// Gets all redirect urls for a content item. + /// + /// The content unique key. + /// All redirect urls for the content item. + IEnumerable GetContentUrls(Guid contentKey); + + /// + /// Gets all redirect urls. + /// + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total); + + /// + /// Gets all redirect urls below a given content item. + /// + /// The content unique identifier. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 1fac280f4e..499c075a83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -179,20 +179,24 @@ namespace Umbraco.Core.Persistence.Repositories // If the stripped-down url returns null, we try again with the original url. // Previously, the function would fail on e.g. "my_x_image.jpg" - var nodeId = GetMediaNodeIdByPath(umbracoFileValue); - if (nodeId < 0) nodeId = GetMediaNodeIdByPath(mediaPath); + var nodeId = GetMediaNodeIdByPath(Sql().Where(x => x.VarChar== umbracoFileValue)); + if (nodeId < 0) nodeId = GetMediaNodeIdByPath(Sql().Where(x => x.VarChar == mediaPath)); + + // If no result so far, try getting from a json value stored in the ntext / nvarchar column + if (nodeId < 0) nodeId = GetMediaNodeIdByPath(Sql().Where("dataNtext LIKE @0", "%" + umbracoFileValue + "%")); + if (nodeId < 0) nodeId = GetMediaNodeIdByPath(Sql().Where("dataNvarchar LIKE @0", "%" + umbracoFileValue + "%")); return nodeId < 0 ? null : Get(nodeId); } - private int GetMediaNodeIdByPath(string url) + private int GetMediaNodeIdByPath(Sql query) { var sql = Sql().SelectAll() .From() .InnerJoin() .On(left => left.PropertyTypeId, right => right.Id) .Where(x => x.Alias == "umbracoFile") - .Where(x => x.VarChar == url); + .Append(query); var dto = Database.Fetch(sql).FirstOrDefault(); return dto?.NodeId ?? -1; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 21f37224d1..eb18989230 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -154,11 +154,11 @@ namespace Umbraco.Core.Persistence.Repositories var sql = Sql() .Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", - "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", + "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID", "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", - "cmsPropertyTypeGroup.text AS PropertyGroupName", + "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs new file mode 100644 index 0000000000..d8cad7b230 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using NPoco; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal class RedirectUrlRepository : NPocoRepositoryBase, IRedirectUrlRepository + { + public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMappingResolver mappingResolver) + : base(work, cache, logger, mappingResolver) + { } + + protected override int PerformCount(IQuery query) + { + throw new NotSupportedException("This repository does not support this method."); + } + + protected override bool PerformExists(int id) + { + return PerformGet(id) != null; + } + + protected override IRedirectUrl PerformGet(int id) + { + var sql = GetBaseQuery(false).Where(x => x.Id == id); + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + if (ids.Length > 2000) + throw new NotSupportedException("This repository does not support more than 2000 ids."); + var sql = GetBaseQuery(false).WhereIn(x => x.Id, ids); + var dtos = Database.Fetch(sql); + return dtos.WhereNotNull().Select(Map); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotSupportedException("This repository does not support this method."); + } + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = Sql(); + if (isCount) + sql.Select(@"COUNT(*) +FROM umbracoRedirectUrl +JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + else + sql.Select(@"umbracoRedirectUrl.*, umbracoNode.id AS contentId +FROM umbracoRedirectUrl +JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoRedirectUrl WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistNewItem(IRedirectUrl entity) + { + var dto = Map(entity); + Database.Insert(dto); + entity.Id = dto.Id; + } + + protected override void PersistUpdatedItem(IRedirectUrl entity) + { + var dto = Map(entity); + Database.Update(dto); + } + + private static RedirectUrlDto Map(IRedirectUrl redirectUrl) + { + if (redirectUrl == null) return null; + + return new RedirectUrlDto + { + Id = redirectUrl.Id, + ContentKey = redirectUrl.ContentKey, + CreateDateUtc = redirectUrl.CreateDateUtc, + Url = redirectUrl.Url, + UrlHash = HashUrl(redirectUrl.Url) + }; + } + + private static IRedirectUrl Map(RedirectUrlDto dto) + { + if (dto == null) return null; + + var url = new RedirectUrl(); + try + { + url.DisableChangeTracking(); + url.Id = dto.Id; + url.ContentId = dto.ContentId; + url.ContentKey = dto.ContentKey; + url.CreateDateUtc = dto.CreateDateUtc; + url.Url = dto.Url; + return url; + } + finally + { + url.EnableChangeTracking(); + } + } + + public IRedirectUrl Get(string url, Guid contentKey) + { + var urlHash = HashUrl(url); + var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey); + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public void DeleteAll() + { + Database.Execute("DELETE FROM umbracoRedirectUrl"); + } + + public void DeleteContentUrls(Guid contentKey) + { + Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); + } + + public void Delete(int id) + { + Database.Delete(id); + } + + public IRedirectUrl GetMostRecentUrl(string url) + { + var urlHash = HashUrl(url); + var sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash) + .OrderByDescending(x => x.CreateDateUtc); + var dtos = Database.Fetch(sql); + var dto = dtos.FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public IEnumerable GetContentUrls(Guid contentKey) + { + var sql = GetBaseQuery(false) + .Where(x => x.ContentKey == contentKey) + .OrderByDescending(x => x.CreateDateUtc); + var dtos = Database.Fetch(sql); + return dtos.Select(Map); + } + + public IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total) + { + var sql = GetBaseQuery(false) + .OrderByDescending(x => x.CreateDateUtc); + var result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + return result.Items.Select(Map); + } + + public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) + { + var sql = GetBaseQuery(false) + .Where("umbracoNode.path LIKE @path", new { path = "%," + rootContentId + ",%" }) + .OrderByDescending(x => x.CreateDateUtc); + var result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + var rules = result.Items.Select(Map); + return rules; + } + + private static string HashUrl(string url) + { + var crypto = new MD5CryptoServiceProvider(); + var inputBytes = Encoding.UTF8.GetBytes(url); + var hashedBytes = crypto.ComputeHash(inputBytes); + return Encoding.UTF8.GetString(hashedBytes); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index 37f249df6e..356bc78ab0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override int PerformCount(IQuery query) { - throw new NotSupportedException("This repository does not support this method"); + throw new NotSupportedException("This repository does not support this method."); } protected override bool PerformExists(int id) @@ -57,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new NotSupportedException("This repository does not support this method"); + throw new NotSupportedException("This repository does not support this method."); } protected override Sql GetBaseQuery(bool isCount) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index 90f0a56696..5cfca7cb3d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -24,12 +24,23 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// See: http://issues.umbraco.org/issue/U4-3876 /// - public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery) + public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In) { - return new Sql(string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)", + + return + new Sql(string.Format( + whereInType == WhereInType.In + ? @"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)" + : @"DELETE FROM {0} WHERE {1} NOT IN (SELECT {1} FROM ({2}) x)", sqlProvider.GetQuotedTableName(tableName), sqlProvider.GetQuotedColumnName(columnName), subQuery.SQL), subQuery.Arguments); } } + + internal enum WhereInType + { + In, + NotIn + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index f6cc1f692b..8e9cc28c7f 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -91,12 +91,17 @@ namespace Umbraco.Core.Persistence EnableSqlTrace = false; } - // fixme: that could be an extension method of IUmbracoDatabaseConfig + // fixme: these two could be an extension method of IUmbracoDatabaseConfig public Sql Sql() { return NPoco.Sql.BuilderFor(new SqlContext(this)); } + public Sql Sql(string sql, params object[] args) + { + return NPoco.Sql.BuilderFor(new SqlContext(this)).Append(sql, args); + } + protected override DbConnection OnConnectionOpened(DbConnection connection) { if (connection == null) throw new ArgumentNullException(nameof(connection)); diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 7bb54e6ef7..636d2c49a8 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -289,11 +289,9 @@ namespace Umbraco.Core.Security } catch (Exception) { - //TODO: Do we need to do more here?? need to make sure that the forms cookie is gone, but is that - // taken care of in our custom middleware somehow? ctx.Authentication.SignOut( - Core.Constants.Security.BackOfficeAuthenticationType, - Core.Constants.Security.BackOfficeExternalAuthenticationType); + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeExternalAuthenticationType); return null; } } diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index f1d18b9d0f..1707813433 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Security /// /// /// - public async override Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) + public override async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) { var result = await base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout); diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index 69be7b60c2..b786a7c93f 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -123,7 +123,7 @@ namespace Umbraco.Core.Security manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); manager.EmailService = new EmailService(); - + //NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it //// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user @@ -204,7 +204,7 @@ namespace Umbraco.Core.Security /// We've allowed this check to be overridden with a simple callback so that developers don't actually /// have to implement/override this class. /// - public async override Task CheckPasswordAsync(T user, string password) + public override async Task CheckPasswordAsync(T user, string password) { if (BackOfficeUserPasswordChecker != null) { diff --git a/src/Umbraco.Core/Security/EmailService.cs b/src/Umbraco.Core/Security/EmailService.cs index 93864aa37c..807d528f3d 100644 --- a/src/Umbraco.Core/Security/EmailService.cs +++ b/src/Umbraco.Core/Security/EmailService.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Security //TODO: This check could be nicer but that is the way it is currently mailMessage.IsBodyHtml = message.Body.IsNullOrWhiteSpace() == false - && message.Body.Contains("<") && message.Body.Contains("/>"); + && message.Body.Contains("<") && message.Body.Contains("(item))) return; + TItem[] deleted; + using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); @@ -590,6 +592,7 @@ namespace Umbraco.Core.Services // all descendants are going to be deleted var descendantsAndSelf = item.DescendantsAndSelf(this) .ToArray(); + deleted = descendantsAndSelf; // all impacted (through composition) probably lose some properties // don't try to be too clever here, just report them all @@ -621,7 +624,7 @@ namespace Umbraco.Core.Services OnChanged(args); } - OnDeleted(new DeleteEventArgs(item, false)); + OnDeleted(new DeleteEventArgs(deleted, false)); Audit(AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, item.Id); } @@ -632,6 +635,8 @@ namespace Umbraco.Core.Services if (OnDeletingCancelled(new DeleteEventArgs(itemsA))) return; + TItem[] deleted; + using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); @@ -639,8 +644,9 @@ namespace Umbraco.Core.Services // all descendants are going to be deleted var allDescendantsAndSelf = itemsA.SelectMany(xx => xx.DescendantsAndSelf(this)) - .Distinct() + .DistinctBy(x => x.Id) .ToArray(); + deleted = allDescendantsAndSelf; // all impacted (through composition) probably lose some properties // don't try to be too clever here, just report them all @@ -671,7 +677,7 @@ namespace Umbraco.Core.Services OnChanged(args); } - OnDeleted(new DeleteEventArgs(itemsA, false)); + OnDeleted(new DeleteEventArgs(deleted, false)); Audit(AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, -1); } diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index e508026a02..e1d4cd75a6 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -28,6 +28,20 @@ namespace Umbraco.Core.Services Func createSubject, Func createBody); + /// + /// Sends the notifications for the specified user regarding the specified nodes and action. + /// + /// + /// + /// + /// + /// + /// + /// + void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody); + /// /// Gets the notifications for the user /// diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs new file mode 100644 index 0000000000..7b21fa9bb7 --- /dev/null +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// + /// + public interface IRedirectUrlService : IService + { + /// + /// Registers a redirect url. + /// + /// The Umbraco url route. + /// The content unique key. + /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. + void Register(string url, Guid contentKey); + + /// + /// Deletes all redirect urls for a given content. + /// + /// The content unique key. + void DeleteContentRedirectUrls(Guid contentKey); + + /// + /// Deletes a redirect url. + /// + /// The redirect url to delete. + void Delete(IRedirectUrl redirectUrl); + + /// + /// Deletes a redirect url. + /// + /// The redirect url identifier. + void Delete(int id); + + /// + /// Deletes all redirect urls. + /// + void DeleteAll(); + + /// + /// Gets the most recent redirect urls corresponding to an Umbraco redirect url route. + /// + /// The Umbraco redirect url route. + /// The most recent redirect urls corresponding to the route. + IRedirectUrl GetMostRecentRedirectUrl(string url); + + /// + /// Gets all redirect urls for a content item. + /// + /// The content unique key. + /// All redirect urls for the content item. + IEnumerable GetContentRedirectUrls(Guid contentKey); + + /// + /// Gets all redirect urls. + /// + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total); + + /// + /// Gets all redirect urls below a given content item. + /// + /// The content unique identifier. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total); + } +} diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index c772eb95f1..59c5b9076d 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -25,20 +25,17 @@ namespace Umbraco.Core.Services private readonly IDatabaseUnitOfWorkProvider _uowProvider; private readonly IUserService _userService; private readonly IContentService _contentService; - private readonly RepositoryFactory _repositoryFactory; private readonly ILogger _logger; - public NotificationService(IDatabaseUnitOfWorkProvider provider, IUserService userService, IContentService contentService, RepositoryFactory repositoryFactory, ILogger logger) + public NotificationService(IDatabaseUnitOfWorkProvider provider, IUserService userService, IContentService contentService, ILogger logger) { - if (provider == null) throw new ArgumentNullException("provider"); - if (userService == null) throw new ArgumentNullException("userService"); - if (contentService == null) throw new ArgumentNullException("contentService"); - if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); - if (logger == null) throw new ArgumentNullException("logger"); + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (userService == null) throw new ArgumentNullException(nameof(userService)); + if (contentService == null) throw new ArgumentNullException(nameof(contentService)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); _uowProvider = provider; _userService = userService; _contentService = contentService; - _repositoryFactory = repositoryFactory; _logger = logger; } @@ -60,37 +57,86 @@ namespace Umbraco.Core.Services Func createBody) { if ((entity is IContent) == false) - { throw new NotSupportedException(); - } + var content = (IContent) entity; - //we'll lazily get these if we need to send notifications - IEnumerable allVersions = null; + + // lazily get versions - into a list to ensure we can enumerate multiple times + List allVersions = null; long totalUsers; var allUsers = _userService.GetAll(0, int.MaxValue, out totalUsers); - foreach (var u in allUsers) + foreach (var u in allUsers.Where(x => x.IsApproved)) { - if (u.IsApproved == false) continue; - var userNotifications = GetUserNotifications(u, content.Path).ToArray(); + var userNotifications = GetUserNotifications(u, content.Path); var notificationForAction = userNotifications.FirstOrDefault(x => x.Action == action); - if (notificationForAction != null) + if (notificationForAction == null) continue; + + if (allVersions == null) // lazy load + allVersions = _contentService.GetVersions(entity.Id).ToList(); + + try { - //lazy load versions if notifications are required - if (allVersions == null) - { - allVersions = _contentService.GetVersions(entity.Id); - } + SendNotification(operatingUser, u, content, allVersions, + actionName, http, createSubject, createBody); + + _logger.Debug($"Notification type: {action} sent to {u.Name} ({u.Email})"); + } + catch (Exception ex) + { + _logger.Error("An error occurred sending notification", ex); + } + } + } + + /// + /// Sends the notifications for the specified user regarding the specified node and action. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Currently this will only work for Content entities! + /// + public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody) + { + if ((entities is IEnumerable) == false) + throw new NotSupportedException(); + + // ensure we can enumerate multiple times + var entitiesL = entities as List ?? entities.Cast().ToList(); + + // lazily get versions - into lists to ensure we can enumerate multiple times + var allVersionsDictionary = new Dictionary>(); + + long totalUsers; + var allUsers = _userService.GetAll(0, int.MaxValue, out totalUsers); + foreach (var u in allUsers.Where(x => x.IsApproved)) + { + var userNotifications = GetUserNotifications(u).ToArray(); + + foreach (var content in entitiesL) + { + var userNotificationsByPath = FilterUserNotificationsByPath(userNotifications, content.Path); + var notificationForAction = userNotificationsByPath.FirstOrDefault(x => x.Action == action); + if (notificationForAction == null) continue; + + var allVersions = allVersionsDictionary.ContainsKey(content.Id) // lazy load + ? allVersionsDictionary[content.Id] + : allVersionsDictionary[content.Id] = _contentService.GetVersions(content.Id).ToList(); try { - SendNotification( - operatingUser, u, content, - allVersions, + SendNotification(operatingUser, u, content, allVersions, actionName, http, createSubject, createBody); - - _logger.Debug(string.Format("Notification type: {0} sent to {1} ({2})", action, u.Name, u.Email)); + _logger.Debug($"Notification type: {action} sent to {u.Name} ({u.Email})"); } catch (Exception ex) { @@ -127,10 +173,20 @@ namespace Umbraco.Core.Services /// public IEnumerable GetUserNotifications(IUser user, string path) { - var userNotifications = GetUserNotifications(user).ToArray(); + var userNotifications = GetUserNotifications(user); + return FilterUserNotificationsByPath(userNotifications, path); + } + + /// + /// Filters a userNotifications collection by a path + /// + /// + /// + /// + public IEnumerable FilterUserNotificationsByPath(IEnumerable userNotifications, string path) + { var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - var result = userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); - return result; + return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); } /// @@ -435,9 +491,9 @@ namespace Umbraco.Core.Services var diffs = Diff.DiffText1(oldText, newText); int pos = 0; - for (int n = 0; n < diffs.Length; n++) + for (var n = 0; n < diffs.Length; n++) { - Diff.Item it = diffs[n]; + var it = diffs[n]; // write unchanged chars while ((pos < it.StartB) && (pos < newText.Length)) @@ -450,7 +506,7 @@ namespace Umbraco.Core.Services if (displayDeletedText && it.DeletedA > 0) { sb.Append(deletedStyle); - for (int m = 0; m < it.DeletedA; m++) + for (var m = 0; m < it.DeletedA; m++) { sb.Append(oldText[it.StartA + m]); } // for diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs new file mode 100644 index 0000000000..8541fe23a8 --- /dev/null +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + internal class RedirectUrlService : RepositoryService, IRedirectUrlService + { + public RedirectUrlService(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, logger, eventMessagesFactory) + { } + + public void Register(string url, Guid contentKey) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + var redir = repo.Get(url, contentKey); + if (redir != null) + redir.CreateDateUtc = DateTime.UtcNow; + else + redir = new RedirectUrl { Url = url, ContentKey = contentKey }; + repo.AddOrUpdate(redir); + uow.Complete(); + } + } + + public void Delete(IRedirectUrl redirectUrl) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + repo.Delete(redirectUrl); + uow.Complete(); + } + } + + public void Delete(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + repo.Delete(id); + uow.Complete(); + } + } + + public void DeleteContentRedirectUrls(Guid contentKey) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + repo.DeleteContentUrls(contentKey); + uow.Complete(); + } + } + + public void DeleteAll() + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + repo.DeleteAll(); + uow.Complete(); + } + } + + public IRedirectUrl GetMostRecentRedirectUrl(string url) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + var rule = repo.GetMostRecentUrl(url); + uow.Complete(); + return rule; + } + } + + public IEnumerable GetContentRedirectUrls(Guid contentKey) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + var rules = repo.GetContentUrls(contentKey); + uow.Complete(); + return rules; + } + } + + public IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + var rules = repo.GetAllUrls(pageIndex, pageSize, out total); + uow.Complete(); + return rules; + } + } + + public IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + var rules = repo.GetAllUrls(rootContentId, pageIndex, pageSize, out total); + uow.Complete(); + return rules; + } + } + } +} diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 178b1b3c00..be9189db6a 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -34,12 +34,13 @@ namespace Umbraco.Core.Services private readonly Lazy _memberGroupService; private readonly Lazy _notificationService; private readonly Lazy _externalLoginService; + private readonly Lazy _redirectUrlService; /// /// Initializes a new instance of the class with lazy services. /// /// Used by IoC. Note that LightInject will favor lazy args when picking a constructor. - public ServiceContext(Lazy migrationEntryService, Lazy publicAccessService, Lazy taskService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService) + public ServiceContext(Lazy migrationEntryService, Lazy publicAccessService, Lazy taskService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService) { _migrationEntryService = migrationEntryService; _publicAccessService = publicAccessService; @@ -68,6 +69,7 @@ namespace Umbraco.Core.Services _memberGroupService = memberGroupService; _notificationService = notificationService; _externalLoginService = externalLoginService; + _redirectUrlService = redirectUrlService; } /// @@ -101,7 +103,8 @@ namespace Umbraco.Core.Services IPublicAccessService publicAccessService = null, IExternalLoginService externalLoginService = null, IMigrationEntryService migrationEntryService = null, - IServerRegistrationService serverRegistrationService = null) + IServerRegistrationService serverRegistrationService = null, + IRedirectUrlService redirectUrlService = null) { if (serverRegistrationService != null) _serverRegistrationService = new Lazy(() => serverRegistrationService); if (migrationEntryService != null) _migrationEntryService = new Lazy(() => migrationEntryService); @@ -130,6 +133,7 @@ namespace Umbraco.Core.Services if (taskService != null) _taskService = new Lazy(() => taskService); if (macroService != null) _macroService = new Lazy(() => macroService); if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); + if (redirectUrlService != null) _redirectUrlService = new Lazy(() => redirectUrlService); } /// @@ -263,5 +267,10 @@ namespace Umbraco.Core.Services public IMemberGroupService MemberGroupService => _memberGroupService.Value; public IExternalLoginService ExternalLoginService => _externalLoginService.Value; + + /// + /// Gets the RedirectUrlService. + /// + public IRedirectUrlService RedirectUrlService => _redirectUrlService.Value; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index dc77314cee..bf3eb23b98 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Sync private readonly ILogger _logger; private int _lastId = -1; private DateTime _lastSync; + private DateTime _lastPruned; private bool _initialized; private bool _syncing; private bool _released; @@ -50,7 +51,7 @@ namespace Umbraco.Core.Sync _appContext = appContext; _options = options; - _lastSync = DateTime.UtcNow; + _lastPruned = _lastSync = DateTime.UtcNow; _syncIdle = new ManualResetEvent(true); _profilingLogger = appContext.ProfilingLogger; _logger = appContext.ProfilingLogger.Logger; @@ -213,6 +214,12 @@ namespace Umbraco.Core.Sync using (_profilingLogger.DebugDuration("Syncing from database...")) { ProcessDatabaseInstructions(); + + if ((DateTime.UtcNow - _lastPruned).TotalSeconds <= _options.PruneThrottleSeconds) + return; + + _lastPruned = _lastSync; + switch (_appContext.GetCurrentServerRole()) { case ServerRole.Single: @@ -235,6 +242,9 @@ namespace Umbraco.Core.Sync /// /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. /// + /// + /// Returns the number of processed instructions + /// private void ProcessDatabaseInstructions() { // NOTE @@ -313,50 +323,53 @@ namespace Umbraco.Core.Sync private void PruneOldInstructions() { var pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions); - var sqlSyntax = _appContext.DatabaseContext.SqlSyntax; - //NOTE: this query could work on SQL server and MySQL: - /* - SELECT id - FROM umbracoCacheInstruction - WHERE utcStamp < getdate() - AND id <> (SELECT MAX(id) FROM umbracoCacheInstruction) - */ - // However, this will not work on SQLCE and in fact it will be slower than the query we are - // using if the SQL server doesn't perform it's own query optimizations (i.e. since the above - // query could actually execute a sub query for every row found). So we've had to go with an - // inner join which is faster and works on SQLCE but it's uglier to read. + // using 2 queries is faster than convoluted joins - var deleteQuery = new Sql().Select("cacheIns.id") - .From("umbracoCacheInstruction cacheIns") - .InnerJoin("(SELECT MAX(id) id FROM umbracoCacheInstruction) tMax") - .On("cacheIns.id <> tMax.id") - .Where("cacheIns.utcStamp < @pruneDate", new {pruneDate = pruneDate}); + var maxId = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction;"); - var deleteSql = sqlSyntax.GetDeleteSubquery( - "umbracoCacheInstruction", - "id", - deleteQuery); + var delete = new Sql().Append(@"DELETE FROM umbracoCacheInstruction WHERE utcStamp < @pruneDate AND id < @maxId", + new { pruneDate, maxId }); - _appContext.DatabaseContext.Database.Execute(deleteSql); + _appContext.DatabaseContext.Database.Execute(delete); } /// /// Ensure that the last instruction that was processed is still in the database. /// - /// If the last instruction is not in the database anymore, then the messenger + /// + /// If the last instruction is not in the database anymore, then the messenger /// should not try to process any instructions, because some instructions might be lost, - /// and it should instead cold-boot. + /// and it should instead cold-boot. + /// However, if the last synced instruction id is '0' and there are '0' records, then this indicates + /// that it's a fresh site and no user actions have taken place, in this circumstance we do not want to cold + /// boot. See: http://issues.umbraco.org/issue/U4-8627 + /// private void EnsureInstructions() { - var sql = _appContext.DatabaseContext.Sql().SelectAll() - .From() - .Where(dto => dto.Id == _lastId); + if (_lastId == 0) + { + var sql = _appContext.DatabaseContext.Sql().Select("COUNT(*)") + .From(); - var dtos = _appContext.DatabaseContext.Database.Fetch(sql); + var count = _appContext.DatabaseContext.Database.ExecuteScalar(sql); - if (dtos.Count == 0) - _lastId = -1; + //if there are instructions but we haven't synced, then a cold boot is necessary + if (count > 0) + _lastId = -1; + } + else + { + var sql = _appContext.DatabaseContext.Sql().SelectAll() + .From() + .Where(dto => dto.Id == _lastId); + + var dtos = _appContext.DatabaseContext.Database.Fetch(sql); + + //if the last synced instruction is not found in the db, then a cold boot is necessary + if (dtos.Count == 0) + _lastId = -1; + } } /// @@ -399,7 +412,7 @@ namespace Umbraco.Core.Sync /// Practically, all we really need is the guid, the other infos are here for information /// and debugging purposes. /// - protected readonly static string LocalIdentity = NetworkHelper.MachineName // eg DOMAIN\SERVER + protected static readonly 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 diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs index 7559c37813..c38a0c2568 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Sync DaysToRetainInstructions = 2; // 2 days ThrottleSeconds = 5; // 5 second MaxProcessingInstructionCount = 1000; + PruneThrottleSeconds = 60; // 1 minute } /// @@ -41,5 +42,10 @@ namespace Umbraco.Core.Sync /// The number of seconds to wait between each sync operations. /// public int ThrottleSeconds { get; set; } + + /// + /// The number of seconds to wait between each prune operations. + /// + public int PruneThrottleSeconds { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6ceea8ce14..b7a3717b66 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -207,6 +207,7 @@ + @@ -264,8 +265,11 @@ + + + @@ -289,6 +293,7 @@ + @@ -327,6 +332,7 @@ + @@ -344,7 +350,14 @@ + + + + + + + @@ -404,6 +417,7 @@ + @@ -411,6 +425,7 @@ + @@ -455,9 +470,12 @@ + + + @@ -1328,6 +1346,7 @@ + diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index d3e5f0b92e..c4227b6276 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -15,6 +15,35 @@ namespace Umbraco.Core.Xml /// public class XmlHelper { + /// + /// Creates or sets an attribute on the XmlNode if an Attributes collection is available + /// + /// + /// + /// + /// + public static void SetAttribute(XmlDocument xml, XmlNode n, string name, string value) + { + if (xml == null) throw new ArgumentNullException("xml"); + if (n == null) throw new ArgumentNullException("n"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + if (n.Attributes == null) + { + return; + } + if (n.Attributes[name] == null) + { + var a = xml.CreateAttribute(name); + a.Value = value; + n.Attributes.Append(a); + } + else + { + n.Attributes[name].Value = value; + } + } + /// /// Gets a value indicating whether a specified string contains only xml whitespace characters. /// @@ -346,6 +375,9 @@ namespace Umbraco.Core.Xml /// a XmlAttribute public static XmlAttribute AddAttribute(XmlDocument xd, string name, string value) { + if (xd == null) throw new ArgumentNullException("xd"); + if (string.IsNullOrEmpty(name)) throw new ArgumentException("Value cannot be null or empty.", "name"); + var temp = xd.CreateAttribute(name); temp.Value = value; return temp; @@ -360,11 +392,37 @@ namespace Umbraco.Core.Xml /// a XmlNode public static XmlNode AddTextNode(XmlDocument xd, string name, string value) { + if (xd == null) throw new ArgumentNullException("xd"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + var temp = xd.CreateNode(XmlNodeType.Element, name, ""); temp.AppendChild(xd.CreateTextNode(value)); return temp; } + /// + /// Sets or Creates a text XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node to set or create the child text node on + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode SetTextNode(XmlDocument xd, XmlNode parent, string name, string value) + { + if (xd == null) throw new ArgumentNullException("xd"); + if (parent == null) throw new ArgumentNullException("parent"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var child = parent.SelectSingleNode(name); + if (child != null) + { + child.InnerText = value; + return child; + } + return AddTextNode(xd, name, value); + } + /// /// Creates a cdata XmlNode with the specified name and value /// @@ -374,11 +432,37 @@ namespace Umbraco.Core.Xml /// A XmlNode public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) { + if (xd == null) throw new ArgumentNullException("xd"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + var temp = xd.CreateNode(XmlNodeType.Element, name, ""); temp.AppendChild(xd.CreateCDataSection(value)); return temp; } + /// + /// Sets or Creates a cdata XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node to set or create the child text node on + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode SetCDataNode(XmlDocument xd, XmlNode parent, string name, string value) + { + if (xd == null) throw new ArgumentNullException("xd"); + if (parent == null) throw new ArgumentNullException("parent"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var child = parent.SelectSingleNode(name); + if (child != null) + { + child.InnerXml = ""; ; + return child; + } + return AddCDataNode(xd, name, value); + } + /// /// Gets the value of a XmlNode /// diff --git a/src/Umbraco.Core/project.json b/src/Umbraco.Core/project.json index 638bec0f85..d621384420 100644 --- a/src/Umbraco.Core/project.json +++ b/src/Umbraco.Core/project.json @@ -1,27 +1,28 @@ { "dependencies": { - "HtmlAgilityPack": "1.4.*", "AutoMapper": "4.2.*", - "LightInject.Annotation": "1.0.*", + "HtmlAgilityPack": "1.4.9.5", + "ImageProcessor": "2.4.*", "LightInject": "4.0.*", + "LightInject.Annotation": "1.0.*", "log4net": "2.0.*", "Microsoft.AspNet.Identity.Core": "2.2.*", "Microsoft.AspNet.Identity.Owin": "2.2.*", - "Microsoft.Owin.Security.OAuth": "3.0.*", "Microsoft.Owin.Security.Cookies": "3.0.*", + "Microsoft.Owin.Security.OAuth": "3.0.*", "MiniProfiler": "3.2.*", + "MySql.Data": "6.9.*", "Newtonsoft.Json": "8.0.*", - "NPoco": "3.3.3", + "NPoco": "3.3.4", "semver": "1.*", - "SqlServerCE": "4.*", "SharpZipLib": "0.86.0", - "MySql.Data": "6.9.*" + "SqlServerCE": "4.*" }, "frameworks": { - "net461": { } + "net461": {} }, "runtimes": { - "win": { }, - "win-anycpu": { } + "win": {}, + "win-anycpu": {} } } \ No newline at end of file diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 211c8322b9..ecb8a7f675 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -209,7 +209,7 @@ - + diff --git a/src/Umbraco.Tests/AttemptTests.cs b/src/Umbraco.Tests/AttemptTests.cs index 5fd4da7f8e..a912fdf2a7 100644 --- a/src/Umbraco.Tests/AttemptTests.cs +++ b/src/Umbraco.Tests/AttemptTests.cs @@ -37,5 +37,19 @@ namespace Umbraco.Tests i => Assert.AreEqual("finished", i), exception => Assert.Fail("Should have been successful.")); } + + [Test] + public void AttemptIf() + { + // just making sure that it is ok to use TryParse as a condition + + int value; + var attempt = Attempt.If(int.TryParse("1234", out value), value); + Assert.IsTrue(attempt.Success); + Assert.AreEqual(1234, attempt.Result); + + attempt = Attempt.If(int.TryParse("12xxx34", out value), value); + Assert.IsFalse(attempt.Success); + } } } diff --git a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs index 39e5dd2cb1..bea89dea0f 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reflection; using System.Web; using NUnit.Framework; @@ -88,7 +89,7 @@ namespace Umbraco.Tests.Cache private static string GetValue(int i) { - Console.WriteLine("get" + i); + Debug.Print("get" + i); if (i < 3) throw new Exception("fail"); return "succ" + i; @@ -101,20 +102,18 @@ namespace Umbraco.Tests.Cache CloneId = Guid.NewGuid(); } - private static readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + } private string _name; public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, WriterSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.WriterSelector); } } public Guid CloneId { get; set; } diff --git a/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs index 87e6d4366e..d1196b7d66 100644 --- a/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Web; using NUnit.Framework; using Umbraco.Core.Cache; @@ -49,7 +50,7 @@ namespace Umbraco.Tests.Cache private static string GetValue(int i) { - Console.WriteLine("get" + i); + Debug.Print("get" + i); if (i < 3) throw new Exception("fail"); return "succ" + i; diff --git a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs index 69a0b35818..d74725610a 100644 --- a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs +++ b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs @@ -810,8 +810,8 @@ namespace Umbraco.Tests.CoreXml // but was NOT working (changing the order of nodes) with macro nav, debug // was due to an issue with macro nav IsSamePosition, fixed - //Console.WriteLine("--------"); - //Console.WriteLine(writer.ToString()); + //Debug.Print("--------"); + //Debug.Print(writer.ToString()); Assert.AreEqual(expected.Lf(), writer.ToString().Lf()); } diff --git a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs index 33965c40c5..550310d164 100644 --- a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs +++ b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; @@ -305,7 +306,7 @@ namespace Umbraco.Tests.DynamicsAndReflection var parameterType = parameters[i].ParameterType; var argumentType = arguments[i].GetType(); - Console.WriteLine("{0} / {1}", parameterType, argumentType); + Debug.Print("{0} / {1}", parameterType, argumentType); if (parameterType == argumentType) continue; // match if (parameterType.IsGenericParameter) // eg T @@ -334,7 +335,7 @@ namespace Umbraco.Tests.DynamicsAndReflection // then what ?! // should _variance_ be of some importance? - Console.WriteLine("generic {0}", argumentType.IsGenericType); + Debug.Print("generic {0}", argumentType.IsGenericType); } else { diff --git a/src/Umbraco.Tests/LibraryTests.cs b/src/Umbraco.Tests/LibraryTests.cs index 3d982d3f90..90aae23e69 100644 --- a/src/Umbraco.Tests/LibraryTests.cs +++ b/src/Umbraco.Tests/LibraryTests.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; - +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -42,7 +42,7 @@ namespace Umbraco.Tests }; var type = new AutoPublishedContentType(0, "anything", propertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = (alias) => type; - Console.WriteLine("INIT LIB {0}", + Debug.Print("INIT LIB {0}", ContentTypesCache.Get(PublishedItemType.Content, "anything") .PropertyTypes.Count()); diff --git a/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs b/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs index ddd1c19095..c5e37f424a 100644 --- a/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs +++ b/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -160,7 +161,7 @@ namespace Umbraco.Tests.Logging // Assert var logsPerSecond = logCount / testDuration.TotalSeconds; - Console.WriteLine("{0} messages logged in {1}s => {2}/s", logCount, testDuration.TotalSeconds, logsPerSecond); + Debug.Print("{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"); } } diff --git a/src/Umbraco.Tests/Logging/DebugAppender.cs b/src/Umbraco.Tests/Logging/DebugAppender.cs index a483103992..db78a96965 100644 --- a/src/Umbraco.Tests/Logging/DebugAppender.cs +++ b/src/Umbraco.Tests/Logging/DebugAppender.cs @@ -5,17 +5,16 @@ using log4net.Core; namespace Umbraco.Tests.Logging { - internal class DebugAppender : MemoryAppender + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// + public 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); diff --git a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs index d347395b1f..88471ab650 100644 --- a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs +++ b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs @@ -14,6 +14,9 @@ using Umbraco.Core.Logging; namespace Umbraco.Tests.Logging { + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// [TestFixture] public class ParallelForwarderTest : IDisposable { @@ -84,7 +87,7 @@ namespace Umbraco.Tests.Logging badAppender .Setup(ba => ba.DoAppend(It.IsAny())) .Throws(new Exception("Bad Appender")); - //.Verifiable(); + //.Verifiable(); // Act log.Info("InitialMessage"); @@ -103,7 +106,7 @@ namespace Umbraco.Tests.Logging const int testSize = 1000; // Arrange - debugAppender.AppendDelay = TimeSpan.FromSeconds(10); + debugAppender.AppendDelay = TimeSpan.FromSeconds(30); var watch = new Stopwatch(); // Act @@ -117,9 +120,7 @@ namespace Umbraco.Tests.Logging // 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; + Debug.Print("Logged {0} errors in {1}ms", testSize, watch.ElapsedMilliseconds); } [Test] @@ -170,11 +171,10 @@ namespace Umbraco.Tests.Logging //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); + Debug.Print("Flushed {0} events during shutdown", numberLoggedAfterClose - numberLoggedBeforeClose); } - [NUnit.Framework.Ignore("Keeps failing very randomly")] - [Test] + [Test, Explicit("Long-running")] public void WillShutdownIfBufferCannotBeFlushedFastEnough() { const int testSize = 250; @@ -206,7 +206,7 @@ namespace Umbraco.Tests.Logging 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); + Debug.Print("Flushed {0} events during shutdown which lasted {1}ms", numberLoggedAfterClose - numberLoggedBeforeClose, watch.ElapsedMilliseconds); } [Test] diff --git a/src/Umbraco.Tests/Logging/RingBufferTest.cs b/src/Umbraco.Tests/Logging/RingBufferTest.cs index 8e80faf8ad..69c58b8631 100644 --- a/src/Umbraco.Tests/Logging/RingBufferTest.cs +++ b/src/Umbraco.Tests/Logging/RingBufferTest.cs @@ -9,6 +9,9 @@ using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Logging { + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// [TestFixture] public class RingBufferTest { diff --git a/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs b/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs index 92ad73aeab..9f185d26be 100644 --- a/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Data.Common; using System.Linq; using Moq; @@ -61,11 +62,11 @@ namespace Umbraco.Tests.Migrations Assert.That(context.Expressions.Any(), Is.True); //Console output - Console.WriteLine("Number of expressions in context: {0}", context.Expressions.Count); - Console.WriteLine(""); + Debug.Print("Number of expressions in context: {0}", context.Expressions.Count); + Debug.Print(""); foreach (var expression in context.Expressions) { - Console.WriteLine(expression.ToString()); + Debug.Print(expression.ToString()); } } } diff --git a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs index c49015f93a..961f52c992 100644 --- a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs +++ b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Data; using System.Linq; using LightInject; @@ -72,7 +73,7 @@ namespace Umbraco.Tests.Migrations //Console output foreach (var expression in context.Expressions) { - Console.WriteLine(expression.ToString()); + Debug.Print(expression.ToString()); } } } diff --git a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs index ae7ad2facf..9c3446be9b 100644 --- a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs @@ -1,11 +1,18 @@ using System; +using System.Diagnostics; using System.Linq; +using Moq; using NUnit.Framework; +using Semver; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight; using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; +using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Tests.Migrations { @@ -13,7 +20,7 @@ namespace Umbraco.Tests.Migrations [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] public class MigrationIssuesTests : BaseDatabaseFactoryTest { - [Test, Ignore("TODO: Ask stephan if he knows why this fails")] + [Test] public void Issue8370Test() { // fixme maybe we need to create some content? @@ -71,14 +78,16 @@ namespace Umbraco.Tests.Migrations { NodeId = n.NodeId, PropertyTypeId = pt.Id, - Text = "text" + Text = "text", + VersionId = Guid.NewGuid() }; DatabaseContext.Database.Insert(data); data = new PropertyDataDto { NodeId = n.NodeId, PropertyTypeId = pt.Id, - Text = "" + Text = "", + VersionId = Guid.NewGuid() }; DatabaseContext.Database.Insert(data); var migrationContext = new MigrationContext(DatabaseContext.Database, Logger); @@ -88,9 +97,53 @@ namespace Umbraco.Tests.Migrations data = DatabaseContext.Database.Fetch("SELECT * FROM cmsPropertyData WHERE id=" + data.Id).FirstOrDefault(); Assert.IsNotNull(data); - Console.WriteLine(data.Text); + Debug.Print(data.Text); Assert.AreEqual("[{\"title\":\"\",\"caption\":\"\",\"link\":\"\",\"newWindow\":false,\"type\":\"external\",\"internal\":null,\"edit\":false,\"isInternal\":false}]", data.Text); } + + [Test] + public void Issue8361Test() + { + var logger = new DebugDiagnosticsLogger(); + + var migrationContext = new MigrationContext(DatabaseContext.Database, Logger); + + //Setup the MigrationRunner + var migrationRunner = new MigrationRunner( + Mock.Of(), + Mock.Of(), + logger, + new SemVersion(7, 5, 0), + new SemVersion(8, 0, 0), + GlobalSettings.UmbracoMigrationName, + + //pass in explicit migrations + new DeleteRedirectUrlTable(migrationContext), + new AddRedirectUrlTable(migrationContext), + new AddRedirectUrlTable2(migrationContext), + new AddRedirectUrlTable3(migrationContext), + new AddRedirectUrlTable4(migrationContext) + ); + + var upgraded = migrationRunner.Execute(migrationContext, true); + Assert.IsTrue(upgraded); + } + + [Migration("8.0.0", 99, GlobalSettings.UmbracoMigrationName)] + public class DeleteRedirectUrlTable : MigrationBase + { + public DeleteRedirectUrlTable(IMigrationContext context) + : base(context) + { } + + public override void Up() + { + Delete.Table("umbracoRedirectUrl"); + } + + public override void Down() + { } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs index d0fcc3d996..64a1b101b0 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs @@ -43,8 +43,10 @@ namespace Umbraco.Tests.Migrations.Upgrades //Create the Sql CE database //Get the connectionstring settings from config var settings = ConfigurationManager.ConnectionStrings[Core.Configuration.GlobalSettings.UmbracoConnectionName]; - var engine = new SqlCeEngine(settings.ConnectionString); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(settings.ConnectionString)) + { + engine.CreateDatabase(); + } } else { diff --git a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs index 918bb88bf1..50be7dc207 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs @@ -73,8 +73,10 @@ namespace Umbraco.Tests.Migrations.Upgrades Resolution.Freeze(); //Create the Sql CE database - var engine = new SqlCeEngine(settings.ConnectionString); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(settings.ConnectionString)) + { + engine.CreateDatabase(); + } } diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 6f54e8624b..7b2863d583 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -412,7 +413,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(content); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } /*[Test] diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index 70208c3424..3ae6a62ee6 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -285,7 +286,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(contentType); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] @@ -390,7 +391,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(contentType); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] @@ -498,7 +499,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(contentType); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs index aebde47553..2a5ad55e1b 100644 --- a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs +++ b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -75,7 +76,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(dtd); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/DictionaryItemTests.cs b/src/Umbraco.Tests/Models/DictionaryItemTests.cs index ff57b5eec6..ab60ccd709 100644 --- a/src/Umbraco.Tests/Models/DictionaryItemTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryItemTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -130,7 +131,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs index 46d6ea1022..c69047d94e 100644 --- a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -74,7 +75,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/LanguageTests.cs b/src/Umbraco.Tests/Models/LanguageTests.cs index 5d79364fb5..dc6b7bf989 100644 --- a/src/Umbraco.Tests/Models/LanguageTests.cs +++ b/src/Umbraco.Tests/Models/LanguageTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -56,7 +57,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index ed354d2ad1..47ef097085 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -219,7 +220,12 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(p.Alias, pDto.Alias); Assert.AreEqual(p.Id, pDto.Id); - Assert.IsTrue(p.Value == null ? pDto.Value == string.Empty : pDto.Value == p.Value); + if (p.Value == null) + Assert.AreEqual(pDto.Value, string.Empty); + else if (p.Value is decimal) + Assert.AreEqual(pDto.Value, ((decimal) p.Value).ToString(NumberFormatInfo.InvariantInfo)); + else + Assert.AreEqual(pDto.Value, p.Value.ToString()); } private void AssertProperty(ContentItemBasic result, Property p) diff --git a/src/Umbraco.Tests/Models/MemberGroupTests.cs b/src/Umbraco.Tests/Models/MemberGroupTests.cs index d39ddc93f3..ed208540ff 100644 --- a/src/Umbraco.Tests/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests/Models/MemberGroupTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -66,7 +67,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(group); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs index 62e3602232..ff1847d8ea 100644 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -150,7 +151,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(member); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyGroupTests.cs b/src/Umbraco.Tests/Models/PropertyGroupTests.cs index ec345051ec..683b9b2adf 100644 --- a/src/Umbraco.Tests/Models/PropertyGroupTests.cs +++ b/src/Umbraco.Tests/Models/PropertyGroupTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -136,7 +137,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(pg); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs index 96f28980ab..82c7521554 100644 --- a/src/Umbraco.Tests/Models/PropertyTypeTests.cs +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -80,7 +81,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(pt); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/RelationTests.cs b/src/Umbraco.Tests/Models/RelationTests.cs index fdc3ae874b..e1d218ef6e 100644 --- a/src/Umbraco.Tests/Models/RelationTests.cs +++ b/src/Umbraco.Tests/Models/RelationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -65,7 +66,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTypeTests.cs b/src/Umbraco.Tests/Models/RelationTypeTests.cs index 2022ab912d..526dfdb3f6 100644 --- a/src/Umbraco.Tests/Models/RelationTypeTests.cs +++ b/src/Umbraco.Tests/Models/RelationTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -59,7 +60,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/StylesheetTests.cs b/src/Umbraco.Tests/Models/StylesheetTests.cs index 21e339f54b..ec6761e0e2 100644 --- a/src/Umbraco.Tests/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests/Models/StylesheetTests.cs @@ -1,14 +1,22 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Models { [TestFixture] public class StylesheetTests { + [SetUp] + public virtual void Initialize() + { + SettingsForTests.Reset(); + } + [Test] public void Can_Create_Stylesheet() { @@ -55,7 +63,10 @@ namespace Umbraco.Tests.Models public void Can_Update_Property() { // Arrange - var stylesheet = new Stylesheet("/css/styles.css") { Content = @"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}" }; + var stylesheet = new Stylesheet("/css/styles.css") + { + Content = @"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}" + }; var prop = stylesheet.Properties.Single(); prop.Alias = "li"; @@ -102,7 +113,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(stylesheet); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTests.cs b/src/Umbraco.Tests/Models/TaskTests.cs index 377746cbaf..54b62fcfa9 100644 --- a/src/Umbraco.Tests/Models/TaskTests.cs +++ b/src/Umbraco.Tests/Models/TaskTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -70,7 +71,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/TaskTypeTests.cs b/src/Umbraco.Tests/Models/TaskTypeTests.cs index e83f8dc3cf..26d3a5d3dd 100644 --- a/src/Umbraco.Tests/Models/TaskTypeTests.cs +++ b/src/Umbraco.Tests/Models/TaskTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -55,7 +56,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TemplateTests.cs b/src/Umbraco.Tests/Models/TemplateTests.cs index a810d9420f..e279851b77 100644 --- a/src/Umbraco.Tests/Models/TemplateTests.cs +++ b/src/Umbraco.Tests/Models/TemplateTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Reflection; using NUnit.Framework; @@ -75,7 +76,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 52513d551d..651e955f33 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -133,7 +134,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index c686a78dc8..189a4a17a8 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -108,7 +109,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTypeTests.cs b/src/Umbraco.Tests/Models/UserTypeTests.cs index 01e4d6fd89..72aa0b2efc 100644 --- a/src/Umbraco.Tests/Models/UserTypeTests.cs +++ b/src/Umbraco.Tests/Models/UserTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index ee91eba99c..9c06b3b361 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -77,11 +77,14 @@ namespace Umbraco.Tests.Persistence // by default the conn string is: Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1; // replace the SDF file with our own and create the sql ce database - var engine = new SqlCeEngine(settings.ConnectionString.Replace("UmbracoNPocoTests", "DatabaseContextTests")); - engine.CreateDatabase(); + var connString = settings.ConnectionString.Replace("UmbracoNPocoTests", "DatabaseContextTests"); + using (var engine = new SqlCeEngine(connString)) + { + engine.CreateDatabase(); + } // re-create the database factory and database context with proper connection string - var dbFactory = new DefaultDatabaseFactory(engine.LocalConnectionString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, new TestScopeContextAdapter(), Mock.Of()); + var dbFactory = new DefaultDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, new TestScopeContextAdapter(), Mock.Of()); _dbContext = new DatabaseContext(dbFactory, _logger); // create application context diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index d926d3ce43..87cab2acf0 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -43,7 +44,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -80,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -121,7 +122,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -152,7 +153,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs index 6c16ce927a..f6001c50f8 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -45,7 +46,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -83,7 +84,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -105,7 +106,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -129,7 +130,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -159,7 +160,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs index 5ba86af69d..c8c30b09c6 100644 --- a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -37,7 +38,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index eec1bd754f..a8dd0b3707 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq.Expressions; using Moq; using NPoco; @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Querying // var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); // var result = modelToSqlExpressionHelper.Visit(predicate); - // Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + // Debug.Print("Model to Sql ExpressionHelper: \n" + result); // Assert.AreEqual("[cmsContentType].[alias] = @0", result); // Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -40,7 +41,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new ContentMapper()); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result); Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new ContentMapper()); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("([umbracoNode].[parentID] = @0)", result); Assert.AreEqual(-1, modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -67,7 +68,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new UserMapper()); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("([umbracoUser].[userLogin] = @0)", result); Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -81,7 +82,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new UserMapper()); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper([umbracoUser].[userLogin]) = upper(@0)", result); Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -98,7 +99,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(sqlContext.SqlSyntax, new UserMapper()); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = upper(@0)", result); Assert.AreEqual("mydomain\\myuser", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -116,7 +117,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper(sqlContext); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Poco to Sql ExpressionHelper: \n" + result); + Debug.Print("Poco to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper(`umbracoUser`.`userLogin`) LIKE upper(@0)", result); Assert.AreEqual("mydomain\\myuser%", modelToSqlExpressionHelper.GetSqlParameters()[0]); diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs index 6fcbd6643e..33214cc3a1 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -40,7 +41,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs index f1893f6ee5..fbf38327b5 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -37,7 +38,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/NPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/NPocoSqlTests.cs index de0081be23..461f934bb2 100644 --- a/src/Umbraco.Tests/Persistence/Querying/NPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/NPocoSqlTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NPoco; using NUnit.Framework; using Umbraco.Core.Models.Rdbms; @@ -186,7 +187,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -205,7 +206,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -219,7 +220,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -233,7 +234,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -247,7 +248,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -267,7 +268,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs index 9844e6fb26..489c13fcf9 100644 --- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -39,7 +40,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(1, result.Arguments.Length); Assert.AreEqual("-1%", sql.Arguments[0]); - Console.WriteLine(strResult); + Debug.Print(strResult); } [Test] @@ -66,7 +67,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(1, result.Arguments.Length); Assert.AreEqual(-1, sql.Arguments[0]); - Console.WriteLine(strResult); + Debug.Print(strResult); } [Test] @@ -92,7 +93,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(1, result.Arguments.Length); Assert.AreEqual("umbTextpage", sql.Arguments[0]); - Console.WriteLine(strResult); + Debug.Print(strResult); } [Test] @@ -122,7 +123,7 @@ namespace Umbraco.Tests.Persistence.Querying var strResult = result.SQL; // Assert - Console.WriteLine(strResult); + Debug.Print(strResult); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index a7ebb2f7cb..cf42f4a739 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -19,6 +19,7 @@ using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; @@ -42,6 +43,14 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out DataTypeDefinitionRepository dtdRepository) + { + TemplateRepository tr; + var ctRepository = CreateRepository(unitOfWork, out contentTypeRepository, out tr); + dtdRepository = new DataTypeDefinitionRepository(unitOfWork, CacheHelper, Logger, contentTypeRepository, MappingResolver); + return ctRepository; + } + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) { TemplateRepository tr; @@ -57,6 +66,69 @@ namespace Umbraco.Tests.Persistence.Repositories return repository; } + /// + /// This test ensures that when property values using special database fields are saved, the actual data in the + /// object being stored is also transformed in the same way as the data being stored in the database is. + /// Before you would see that ex: a decimal value being saved as 100 or "100", would be that exact value in the + /// object, but the value saved to the database was actually 100.000000. + /// When querying the database for the value again - the value would then differ from what is in the object. + /// This caused inconsistencies between saving+publishing and simply saving and then publishing, due to the former + /// sending the non-transformed data directly on to publishing. + /// + [Test] + public void Property_Values_With_Special_DatabaseTypes_Are_Equal_Before_And_After_Being_Persisted() + { + var provider = TestObjects.GetDatabaseUnitOfWorkProvider(Logger); + using (var unitOfWork = provider.CreateUnitOfWork()) + { + ContentTypeRepository contentTypeRepository; + DataTypeDefinitionRepository dataTypeDefinitionRepository; + + var repository = CreateRepository(unitOfWork, out contentTypeRepository, out dataTypeDefinitionRepository); + + // Setup + var dtd = new DataTypeDefinition(-1, Constants.PropertyEditors.DecimalAlias) { Name = "test", DatabaseType = DataTypeDatabaseType.Decimal }; + dataTypeDefinitionRepository.AddOrUpdate(dtd); + unitOfWork.Complete(); + + const string decimalPropertyAlias = "decimalProperty"; + const string intPropertyAlias = "intProperty"; + const string dateTimePropertyAlias = "datetimeProperty"; + var dateValue = new DateTime(2016, 1, 6); + + var propertyTypeCollection = new PropertyTypeCollection( + new List + { + MockedPropertyTypes.CreateDecimalProperty(decimalPropertyAlias, "Decimal property", dtd.Id), + MockedPropertyTypes.CreateIntegerProperty(intPropertyAlias, "Integer property"), + MockedPropertyTypes.CreateDateTimeProperty(dateTimePropertyAlias, "DateTime property") + }); + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage", propertyTypeCollection); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Complete(); + + // Int and decimal values are passed in as strings as they would be from the backoffice UI + var textpage = MockedContent.CreateSimpleContentWithSpecialDatabaseTypes(contentType, "test@umbraco.org", -1, "100", "150", dateValue); + + // Act + repository.AddOrUpdate(textpage); + unitOfWork.Complete(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + + var persistedTextpage = repository.Get(textpage.Id); + Assert.That(persistedTextpage.Name, Is.EqualTo(textpage.Name)); + Assert.AreEqual(100m, persistedTextpage.GetValue(decimalPropertyAlias)); + Assert.AreEqual(persistedTextpage.GetValue(decimalPropertyAlias), textpage.GetValue(decimalPropertyAlias)); + Assert.AreEqual(150, persistedTextpage.GetValue(intPropertyAlias)); + Assert.AreEqual(persistedTextpage.GetValue(intPropertyAlias), textpage.GetValue(intPropertyAlias)); + Assert.AreEqual(dateValue, persistedTextpage.GetValue(dateTimePropertyAlias)); + Assert.AreEqual(persistedTextpage.GetValue(dateTimePropertyAlias), textpage.GetValue(dateTimePropertyAlias)); + } + } + [Test] public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() { @@ -101,7 +173,7 @@ namespace Umbraco.Tests.Persistence.Repositories ContentTypeRepository contentTypeRepository; var repository = CreateRepository(unitOfWork, out contentTypeRepository); ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); - Content textpage = MockedContent.CreateSimpleContent(contentType); + IContent textpage = MockedContent.CreateSimpleContent(contentType); // Act contentTypeRepository.AddOrUpdate(contentType); @@ -141,11 +213,15 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(textpage); unitOfWork.Flush(); + var fetched = repository.Get(textpage.Id); + // Assert Assert.That(textpage.Template, Is.Not.Null); Assert.That(textpage.Template, Is.EqualTo(contentType.DefaultTemplate)); unitOfWork.Complete(); + + TestHelper.AssertAllPropertyValuesAreEquals(textpage, fetched, "yyyy-MM-dd HH:mm:ss"); } } @@ -539,11 +615,11 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository(unitOfWork, out contentTypeRepository); // Act var query = repository.Query.Where(x => x.Level == 2); + long totalRecords; var filterQuery = new Query(SqlSyntax, MappingResolver).Where(x => x.Name.Contains("Page 2")); - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, - filterQuery); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, filterQuery); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -563,11 +639,11 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository(unitOfWork, out contentTypeRepository); // Act var query = repository.Query.Where(x => x.Level == 2); + long totalRecords; var filterQuery = new Query(SqlSyntax, MappingResolver).Where(x => x.Name.Contains("text")); - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, - filterQuery); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, filterQuery); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 751e3e2f35..e6ca809f1c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -255,6 +255,8 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(contentType); unitOfWork.Flush(); + var fetched = repository.Get(contentType.Id); + // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(contentType.PropertyGroups.All(x => x.HasIdentity), Is.True); @@ -270,6 +272,8 @@ namespace Umbraco.Tests.Persistence.Repositories { Assert.AreNotEqual(propertyType.Key, Guid.Empty); } + + TestHelper.AssertAllPropertyValuesAreEquals(contentType, fetched, "yyyy-MM-dd HH:mm:ss", ignoreProperties: new [] { "DefaultTemplate", "AllowedTemplates", "UpdateDate" }); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 6183aad18b..679d5b5bd9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -353,9 +353,13 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Flush(); var exists = repository.Exists(dataTypeDefinition.Id); + var fetched = repository.Get(dataTypeDefinition.Id); + // Assert Assert.That(dataTypeDefinition.HasIdentity, Is.True); Assert.That(exists, Is.True); + + TestHelper.AssertAllPropertyValuesAreEquals(dataTypeDefinition, fetched, "yyyy-MM-dd HH:mm:ss"); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 0fb798c1b3..6ca383ff1c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -59,9 +59,13 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(image); unitOfWork.Flush(); + var fetched = repository.Get(image.Id); + // Assert Assert.That(mediaType.HasIdentity, Is.True); Assert.That(image.HasIdentity, Is.True); + + TestHelper.AssertAllPropertyValuesAreEquals(image, fetched, "yyyy-MM-dd HH:mm:ss"); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index dda9ce490b..542e991392 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -191,11 +191,15 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(contentType); unitOfWork.Flush(); + var fetched = repository.Get(contentType.Id); + // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(contentType.PropertyGroups.All(x => x.HasIdentity), Is.True); Assert.That(contentType.Path.Contains(","), Is.True); - Assert.That(contentType.SortOrder, Is.GreaterThan(0)); + Assert.That(contentType.SortOrder, Is.GreaterThan(0)); + + TestHelper.AssertAllPropertyValuesAreEquals(contentType, fetched, "yyyy-MM-dd HH:mm:ss", ignoreProperties: new[] { "UpdateDate" }); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 4bc5f2862c..3c079d9ed6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Xml.Linq; using Moq; @@ -156,6 +157,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(sut.Email, Is.EqualTo("johnny@example.com")); Assert.That(sut.RawPasswordValue, Is.EqualTo("123")); Assert.That(sut.Username, Is.EqualTo("hefty")); + + TestHelper.AssertAllPropertyValuesAreEquals(sut, member, "yyyy-MM-dd HH:mm:ss"); } } @@ -269,7 +272,7 @@ namespace Umbraco.Tests.Persistence.Repositories .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); Assert.That(sql.SQL, Is.Not.Empty); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 8b85f5de17..da24f24075 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var repository = CreateRepository(unitOfWork); - var memberType = MockedContentTypes.CreateSimpleMemberType(); + var memberType = (IMemberType) MockedContentTypes.CreateSimpleMemberType(); repository.AddOrUpdate(memberType); unitOfWork.Flush(); @@ -58,6 +58,34 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(sut.PropertyGroups.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); Assert.That(sut.PropertyTypes.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); + + TestHelper.AssertAllPropertyValuesAreEquals(sut, memberType, "yyyy-MM-dd HH:mm:ss"); + } + } + + [Test] + public void Can_Persist_Member_Type_Same_Property_Keys() + { + var provider = TestObjects.GetDatabaseUnitOfWorkProvider(Logger); + using (var unitOfWork = provider.CreateUnitOfWork()) + { + var repository = CreateRepository(unitOfWork); + + var memberType = (IMemberType)MockedContentTypes.CreateSimpleMemberType(); + + repository.AddOrUpdate(memberType); + unitOfWork.Complete(); + + var propertyKeys = memberType.PropertyTypes.Select(x => x.Key).OrderBy(x => x).ToArray(); + var groupKeys = memberType.PropertyGroups.Select(x => x.Key).OrderBy(x => x).ToArray(); + + memberType = repository.Get(memberType.Id); + var propertyKeys2 = memberType.PropertyTypes.Select(x => x.Key).OrderBy(x => x).ToArray(); + var groupKeys2 = memberType.PropertyGroups.Select(x => x.Key).OrderBy(x => x).ToArray(); + + Assert.IsTrue(propertyKeys.SequenceEqual(propertyKeys2)); + Assert.IsTrue(groupKeys.SequenceEqual(groupKeys2)); + } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs new file mode 100644 index 0000000000..7891ce0733 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs @@ -0,0 +1,229 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class RedirectUrlRepositoryTests : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + CreateTestData(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void CanSaveAndGet() + { + var provider = TestObjects.GetDatabaseUnitOfWorkProvider(Logger); + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + var rurl = repo.GetMostRecentUrl("blah"); + uow.Complete(); + + Assert.IsNotNull(rurl); + Assert.AreEqual(_textpage.Id, rurl.ContentId); + } + } + + [Test] + public void CanSaveAndGetMostRecent() + { + var provider = TestObjects.GetDatabaseUnitOfWorkProvider(Logger); + + Assert.AreNotEqual(_textpage.Id, _otherpage.Id); + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + + // fixme - too fast = same date = key violation? + // and... can that happen in real life? + // we don't really *care* about the IX, only supposed to make things faster... + // BUT in realife we AddOrUpdate in a trx so it should be safe, always + + rurl = new RedirectUrl + { + ContentKey = _otherpage.Key, + Url = "blah", + CreateDateUtc = rurl.CreateDateUtc.AddSeconds(1) // ensure time difference + }; + repo.AddOrUpdate(rurl); + uow.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + var rurl = repo.GetMostRecentUrl("blah"); + uow.Complete(); + + Assert.IsNotNull(rurl); + Assert.AreEqual(_otherpage.Id, rurl.ContentId); + } + } + + [Test] + public void CanSaveAndGetByContent() + { + var provider = TestObjects.GetDatabaseUnitOfWorkProvider(Logger); + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + + // fixme - goes too fast and bam, errors, first is blah + + rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "durg", + CreateDateUtc = rurl.CreateDateUtc.AddSeconds(1) // ensure time difference + }; + repo.AddOrUpdate(rurl); + uow.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + var rurls = repo.GetContentUrls(_textpage.Key).ToArray(); + uow.Complete(); + + Assert.AreEqual(2, rurls.Length); + Assert.AreEqual("durg", rurls[0].Url); + Assert.AreEqual("blah", rurls[1].Url); + } + } + + [Test] + public void CanSaveAndDelete() + { + var provider = TestObjects.GetDatabaseUnitOfWorkProvider(Logger); + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + + rurl = new RedirectUrl + { + ContentKey = _otherpage.Key, + Url = "durg" + }; + repo.AddOrUpdate(rurl); + uow.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.CreateUnitOfWork()) + { + var repo = CreateRepository(uow); + repo.DeleteContentUrls(_textpage.Key); + uow.Complete(); + + var rurls = repo.GetContentUrls(_textpage.Key); + + Assert.AreEqual(0, rurls.Count()); + } + } + + private IRedirectUrlRepository CreateRepository(IDatabaseUnitOfWork uow) + { + return new RedirectUrlRepository(uow, CacheHelper, Logger, MappingResolver); + } + + private IContent _textpage, _subpage, _otherpage, _trashed; + + public void CreateTestData() + { + //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); + contentType.Key = Guid.NewGuid(); + ServiceContext.ContentTypeService.Save(contentType); + + //Create and Save Content "Homepage" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 1) + _textpage = MockedContent.CreateSimpleContent(contentType); + _textpage.Key = Guid.NewGuid(); + ServiceContext.ContentService.Save(_textpage); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 2) + _subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", _textpage.Id); + _subpage.Key = Guid.NewGuid(); + ServiceContext.ContentService.Save(_subpage); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 3) + _otherpage = MockedContent.CreateSimpleContent(contentType, "Text Page 2", _textpage.Id); + _otherpage.Key = Guid.NewGuid(); + ServiceContext.ContentService.Save(_otherpage); + + //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 4) + _trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); + _trashed.Key = Guid.NewGuid(); + ((Content) _trashed).Trashed = true; + ServiceContext.ContentService.Save(_trashed); + } + } +} diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index 72e58295a6..767069da8c 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Data.Common; using Moq; using NPoco; @@ -58,16 +59,16 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, var indexes = SqlContext.SqlSyntax.Format(definition.Indexes); var keys = SqlContext.SqlSyntax.Format(definition.ForeignKeys); - Console.WriteLine(create); - Console.WriteLine(primaryKey); + Debug.Print(create); + Debug.Print(primaryKey); foreach (var sql in keys) { - Console.WriteLine(sql); + Debug.Print(sql); } foreach (var sql in indexes) { - Console.WriteLine(sql); + Debug.Print(sql); } } diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index 6f986e8fe9..ec49b2b49a 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -285,7 +285,7 @@ namespace Umbraco.Tests.Plugins public void Resolves_Trees() { var trees = _manager.ResolveTrees(); - Assert.AreEqual(6, trees.Count()); // 6 classes in the solution implement BaseTree + Assert.AreEqual(5, trees.Count()); // 5 classes in the solution implement BaseTree } [Test] diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index fc1fc71ada..d8ce512b46 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -26,11 +26,11 @@ using Umbraco.Web.Trees; namespace Umbraco.Tests.Plugins { - + /// /// Tests for typefinder /// - [TestFixture] + [TestFixture] public class TypeFinderTests { /// @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Plugins { _assemblies = new[] { - this.GetType().Assembly, + this.GetType().Assembly, typeof(SqlCEHelper).Assembly, typeof(CMSNode).Assembly, typeof(System.Guid).Assembly, @@ -97,7 +97,7 @@ namespace Umbraco.Tests.Plugins Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); - Assert.AreEqual(23, typesFound.Count()); // 23 classes in Umbraco.Web are marked with [Tree] + Assert.AreEqual(22, typesFound.Count()); // 22 classes in Umbraco.Web are marked with [Tree] } [Ignore] @@ -128,7 +128,7 @@ namespace Umbraco.Tests.Plugins } } } - + } [Ignore] @@ -159,7 +159,7 @@ namespace Umbraco.Tests.Plugins } } } - + } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] @@ -201,7 +201,7 @@ namespace Umbraco.Tests.Plugins /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder /// /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been /// loaded in the CLR, not all assemblies. /// See these threads: /// http://issues.umbraco.org/issue/U5-198 @@ -327,8 +327,8 @@ namespace Umbraco.Tests.Plugins } catch (SecurityException) { - //we will just ignore this because this will fail - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to + //we will just ignore this because this will fail + //in medium trust for system assemblies, we get an exception but we just want to continue until we get to //an assembly that is ok. } } @@ -345,9 +345,9 @@ namespace Umbraco.Tests.Plugins } catch (SecurityException) { - //we will just ignore this because if we are trying to do a call to: + //we will just ignore this because if we are trying to do a call to: // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to + //in medium trust for system assemblies, we get an exception but we just want to continue until we get to //an assembly that is ok. } } @@ -359,7 +359,7 @@ namespace Umbraco.Tests.Plugins } /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan + /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are /// cached for perforance reasons. /// @@ -427,7 +427,7 @@ namespace Umbraco.Tests.Plugins "RouteDebugger,", "SqlCE4Umbraco,", "umbraco.datalayer,", - "umbraco.interfaces,", + "umbraco.interfaces,", "umbraco.providers,", "Umbraco.Web.UI,", "umbraco.webservices", @@ -629,5 +629,5 @@ namespace Umbraco.Tests.Plugins } } - + } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 81f9cc8fa8..486cff70f5 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -112,7 +112,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items saved in {0} ms", elapsed); + Debug.Print("100 content items saved in {0} ms", elapsed); // Assert Assert.That(pages.Any(x => x.HasIdentity == false), Is.False); @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items saved in {0} ms", elapsed); + Debug.Print("100 content items saved in {0} ms", elapsed); // Assert Assert.That(pages.Any(x => x.HasIdentity == false), Is.False); @@ -159,7 +159,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items retrieved in {0} ms without caching", elapsed); + Debug.Print("100 content items retrieved in {0} ms without caching", elapsed); // Assert Assert.That(contents.Any(x => x.HasIdentity == false), Is.False); @@ -191,7 +191,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("1000 content items retrieved in {0} ms without caching", elapsed); + Debug.Print("1000 content items retrieved in {0} ms without caching", elapsed); // Assert //Assert.That(contents.Any(x => x.HasIdentity == false), Is.False); @@ -223,7 +223,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items retrieved in {0} ms with caching", elapsed); + Debug.Print("100 content items retrieved in {0} ms with caching", elapsed); // Assert Assert.That(contentsCached.Any(x => x.HasIdentity == false), Is.False); @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("1000 content items retrieved in {0} ms with caching", elapsed); + Debug.Print("1000 content items retrieved in {0} ms with caching", elapsed); // Assert //Assert.That(contentsCached.Any(x => x.HasIdentity == false), Is.False); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index f5d8d62e89..deaa39435d 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using Moq; @@ -1639,7 +1640,7 @@ namespace Umbraco.Tests.Services list.Add(content); list.AddRange(CreateChildrenOf(contentType, content, 4)); - Console.WriteLine("Created: 'Hierarchy Simple Text Page {0}'", i); + Debug.Print("Created: 'Hierarchy Simple Text Page {0}'", i); } return list; @@ -1653,7 +1654,7 @@ namespace Umbraco.Tests.Services var c = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage " + i, content); list.Add(c); - Console.WriteLine("Created: 'Hierarchy Simple Text Subpage {0}' - Depth: {1}", i, depth); + Debug.Print("Created: 'Hierarchy Simple Text Subpage {0}' - Depth: {1}", i, depth); } return list; } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index d9ff7f5c7c..34866be8b9 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -7,13 +7,13 @@ using Umbraco.Core; using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; - +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services { - + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture, RequiresSTA] [TestSetup.FacadeService(EnableRepositoryEvents = true)] @@ -275,6 +275,66 @@ namespace Umbraco.Tests.Services Assert.That(success, Is.False); } + [Test] + public void Deleting_ContentType_Sends_Correct_Number_Of_DeletedEntities_In_Events() + { + var cts = ServiceContext.ContentTypeService; + var deletedEntities = 0; + var contentType = MockedContentTypes.CreateSimpleContentType("page", "Page"); + cts.Save(contentType); + + ContentTypeService.Deleted += (sender, args) => + { + deletedEntities += args.DeletedEntities.Count(); + }; + + cts.Delete(contentType); + + Assert.AreEqual(deletedEntities, 1); + } + + [Test] + public void Deleting_Multiple_ContentTypes_Sends_Correct_Number_Of_DeletedEntities_In_Events() + { + var cts = ServiceContext.ContentTypeService; + var deletedEntities = 0; + var contentType = MockedContentTypes.CreateSimpleContentType("page", "Page"); + cts.Save(contentType); + var contentType2 = MockedContentTypes.CreateSimpleContentType("otherPage", "Other page"); + cts.Save(contentType2); + + ContentTypeService.Deleted += (sender, args) => + { + deletedEntities += args.DeletedEntities.Count(); + }; + + cts.Delete(contentType); + cts.Delete(contentType2); + + Assert.AreEqual(2, deletedEntities); + } + + [Test] + public void Deleting_ContentType_With_Child_Sends_Correct_Number_Of_DeletedEntities_In_Events() + { + var cts = ServiceContext.ContentTypeService; + var deletedEntities = 0; + var contentType = MockedContentTypes.CreateSimpleContentType("page", "Page"); + cts.Save(contentType); + var contentType2 = MockedContentTypes.CreateSimpleContentType("subPage", "Sub page"); + contentType2.ParentId = contentType.Id; + cts.Save(contentType2); + + ContentTypeService.Deleted += (sender, args) => + { + deletedEntities += args.DeletedEntities.Count(); + }; + + cts.Delete(contentType); + + Assert.AreEqual(2, deletedEntities); + } + [Test] public void Can_Remove_ContentType_Composition_From_ContentType() { @@ -574,7 +634,7 @@ namespace Umbraco.Tests.Services * -- Content Page * ---- Advanced Page -> Content Meta * Content Meta :: Composition, has 'Title' - * + * * Content Meta has 'Title' PropertyType * Adding 'Title' to BasePage should fail */ @@ -605,7 +665,7 @@ namespace Umbraco.Tests.Services }; var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); service.Save(contentPage); - + var compositionAdded = advancedPage.AddContentType(contentMetaComposition); service.Save(advancedPage); @@ -764,7 +824,7 @@ namespace Umbraco.Tests.Services }; var titleAdded = seoComposition.AddPropertyType(titlePropertyType, "Content"); service.Save(seoComposition); - + var seoCompositionAdded = advancedPage.AddContentType(seoComposition); var metaCompositionAdded = moreAdvancedPage.AddContentType(metaComposition); service.Save(advancedPage); @@ -871,7 +931,7 @@ namespace Umbraco.Tests.Services var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, "author") { - Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = advancedPage.AddPropertyType(authorPropertyType, "Content"); service.Save(basePage); @@ -945,7 +1005,7 @@ namespace Umbraco.Tests.Services public void Can_Rename_PropertyGroup_With_Inherited_PropertyGroups() { //Related the first issue in screencast from this post http://issues.umbraco.org/issue/U4-5986 - + // Arrange var service = ServiceContext.ContentTypeService; @@ -1218,8 +1278,8 @@ namespace Umbraco.Tests.Services * - Content Page * -- Advanced Page * Content Meta :: Composition - */ - + */ + // Arrange var service = ServiceContext.ContentTypeService; var basePage = MockedContentTypes.CreateBasicContentType(); diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 832d26d0c8..b8b78298b0 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -97,6 +98,40 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Get_Media_By_Path() + { + var mediaService = ServiceContext.MediaService; + var mediaType = MockedContentTypes.CreateImageMediaType("Image2"); + ServiceContext.MediaTypeService.Save(mediaType); + + var media = MockedMedia.CreateMediaImage(mediaType, -1); + mediaService.Save(media); + + var mediaPath = "/media/test-image.png"; + var resolvedMedia = mediaService.GetMediaByPath(mediaPath); + + Assert.IsNotNull(resolvedMedia); + Assert.That(resolvedMedia.GetValue(Constants.Conventions.Media.File).ToString() == mediaPath); + } + + [Test] + public void Can_Get_Media_With_Crop_By_Path() + { + var mediaService = ServiceContext.MediaService; + var mediaType = MockedContentTypes.CreateImageMediaType("Image2"); + ServiceContext.MediaTypeService.Save(mediaType); + + var media = MockedMedia.CreateMediaImageWithCrop(mediaType, -1); + mediaService.Save(media); + + var mediaPath = "/media/test-image.png"; + var resolvedMedia = mediaService.GetMediaByPath(mediaPath); + + Assert.IsNotNull(resolvedMedia); + Assert.That(resolvedMedia.GetValue(Constants.Conventions.Media.File).ToString().Contains(mediaPath)); + } + private Tuple CreateTrashedTestMedia() { //Create and Save folder-Media -> 1050 diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs index 02bfa1293e..5b3db6c05e 100644 --- a/src/Umbraco.Tests/Services/PackagingServiceTests.cs +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; @@ -42,7 +43,7 @@ namespace Umbraco.Tests.Services Assert.That(element, Is.Not.Null); Assert.That(element.Element("name").Value, Is.EqualTo("Test")); Assert.That(element.Element("alias").Value, Is.EqualTo("test1")); - Console.Write(element.ToString()); + Debug.Print(element.ToString()); } [Test] diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index 68e28d7cff..7925ac0d1e 100644 --- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; @@ -353,9 +354,9 @@ namespace Umbraco.Tests.Strings // then next string element is one char and 3 bytes, 16 bits of code point Assert.AreEqual('t', bytes[9]); //foreach (var b in bytes) - // Console.WriteLine("{0:X}", b); + // Debug.Print("{0:X}", b); - Console.WriteLine("\U00010B70"); + Debug.Print("\U00010B70"); } [Test] diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 07ac002a48..199bd9254d 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Globalization; using NUnit.Framework; using Umbraco.Core; @@ -31,8 +32,8 @@ namespace Umbraco.Tests.Strings [TestCase("hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", true)] public void String_To_Guid(string first, string second, bool result) { - Console.WriteLine("First: " + first.ToGuid()); - Console.WriteLine("Second: " + second.ToGuid()); + Debug.Print("First: " + first.ToGuid()); + Debug.Print("Second: " + second.ToGuid()); Assert.AreEqual(result, first.ToGuid() == second.ToGuid()); } diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 440497a316..6dcc29d709 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -34,9 +34,10 @@ using File = System.IO.File; namespace Umbraco.Tests.TestHelpers { /// - /// Use this abstract class for tests that requires a Sql Ce database populated with the umbraco db schema. - /// The NPoco Database class should be used through the . + /// Provides a base class for Umbraco application tests that require a database. /// + /// Can provide a SqlCE database populated with the Umbraco schema. The database should be accessed + /// through the . [TestFixture, RequiresSTA] public abstract class BaseDatabaseFactoryTest : BaseUmbracoApplicationTest { @@ -103,9 +104,17 @@ namespace Umbraco.Tests.TestHelpers // create the database factory - if the test does not require an actual database, // use a mock factory; otherwise use a real factory. - var databaseFactory = DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture - ? TestObjects.GetIDatabaseFactoryMock() - : new DefaultDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, Logger, new TestScopeContextAdapter(), MappingResolver); + IDatabaseFactory databaseFactory; + if (DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture) + { + databaseFactory = TestObjects.GetIDatabaseFactoryMock(); + } + else + { + var f = new DefaultDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, Logger, new TestScopeContextAdapter(), MappingResolver); + f.ResetForTests(); + databaseFactory = f; + } // so, using the above code to create a mock IDatabaseFactory if we don't have a real database // but, that will NOT prevent _appContext from NOT being configured, because it cannot connect @@ -159,6 +168,13 @@ namespace Umbraco.Tests.TestHelpers } } + protected virtual ISqlSyntaxProvider SqlSyntax => GetSyntaxProvider(); + + protected virtual ISqlSyntaxProvider GetSyntaxProvider() + { + return new SqlCeSyntaxProvider(); + } + protected virtual string GetDbProviderName() { return Constants.DbProviderNames.SqlCe; @@ -222,8 +238,10 @@ namespace Umbraco.Tests.TestHelpers } else { - var engine = new SqlCeEngine(settings.ConnectionString); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(settings.ConnectionString)) + { + engine.CreateDatabase(); + } } } @@ -376,23 +394,21 @@ namespace Umbraco.Tests.TestHelpers } } } - if (_firstTestInFixture) + if (_firstTestInFixture == false) return; + + lock (Locker) { - lock (Locker) - { - if (_firstTestInFixture) - { - _isFirstTestInFixture = true; //set the flag - _firstTestInFixture = false; - } - } + if (_firstTestInFixture == false) return; + + _isFirstTestInFixture = true; //set the flag + _firstTestInFixture = false; } } private void RemoveDatabaseFile(UmbracoDatabase database, Action onFail = null) { CloseDbConnections(database); - string path = TestHelper.CurrentAssemblyDirectory; + var path = TestHelper.CurrentAssemblyDirectory; try { string filePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index b21eb5e25a..0eb2eb9cc8 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -30,13 +30,12 @@ using UmbracoExamine; namespace Umbraco.Tests.TestHelpers { /// - /// A base test class used for umbraco tests whcih sets up the logging, plugin manager any base resolvers, etc... and - /// ensures everything is torn down properly. + /// Provides a base class for Umbraco application tests. /// + /// Sets logging, pluging manager, application context, base resolvers... [TestFixture] public abstract class BaseUmbracoApplicationTest : BaseUmbracoConfigurationTest { - [TestFixtureSetUp] public void InitializeFixture() { @@ -76,18 +75,19 @@ namespace Umbraco.Tests.TestHelpers { base.TearDown(); - //reset settings + // reset settings SettingsForTests.Reset(); UmbracoContext.Current = null; TestHelper.CleanContentDirectories(); TestHelper.CleanUmbracoSettingsConfig(); - //reset the app context, this should reset most things that require resetting like ALL resolvers + + // reset the app context, this should reset most things that require resetting like ALL resolvers ApplicationContext.Current.DisposeIfDisposable(); ApplicationContext.Current = null; + + // reset plugin manager ResetPluginManager(); - Container.Dispose(); - } protected virtual void ConfigureContainer() @@ -144,7 +144,7 @@ namespace Umbraco.Tests.TestHelpers { if (LegacyPropertyEditorIdToAliasConverter.Count() == 0) { - //Create the legacy prop-eds mapping + // create the legacy prop-eds mapping LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); } } @@ -159,7 +159,7 @@ namespace Umbraco.Tests.TestHelpers /// private void InitializeMappers() { - if (this.GetType().GetCustomAttribute(false) != null) + if (GetType().GetCustomAttribute(false) != null) { Mapper.Initialize(configuration => { @@ -175,7 +175,7 @@ namespace Umbraco.Tests.TestHelpers /// /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs - /// to be set to true if the SetupPluginManager has been overridden. + /// to be set to true if the SetupPluginManager has been overridden. /// protected virtual bool PluginManagerResetRequired { @@ -215,14 +215,16 @@ namespace Umbraco.Tests.TestHelpers protected virtual ApplicationContext CreateApplicationContext() { var evtMsgs = new TransientEventMessagesFactory(); + var dbFactory = new DefaultDatabaseFactory( + Core.Configuration.GlobalSettings.UmbracoConnectionName, + TestObjects.GetDefaultSqlSyntaxProviders(Logger), + Logger, new TestScopeContextAdapter(), + Mock.Of()); + dbFactory.ResetForTests(); var applicationContext = new ApplicationContext( - //assign the db context - new DatabaseContext(new DefaultDatabaseFactory( - Core.Configuration.GlobalSettings.UmbracoConnectionName, - TestObjects.GetDefaultSqlSyntaxProviders(Logger), - Logger, new TestScopeContextAdapter(), - Mock.Of()), Logger), - //assign the service context + // assign the db context + new DatabaseContext(dbFactory, Logger), + // assign the service context TestObjects.GetServiceContext( Container.GetInstance(), TestObjects.GetDatabaseUnitOfWorkProvider(Logger), @@ -273,7 +275,9 @@ namespace Umbraco.Tests.TestHelpers protected ApplicationContext ApplicationContext => ApplicationContext.Current; protected ILogger Logger => ProfilingLogger.Logger; + protected ProfilingLogger ProfilingLogger { get; private set; } + protected CacheHelper CacheHelper { get; private set; } //I know tests shouldn't use IoC, but for all these tests inheriting from this class are integration tests diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index 5570096ba5..8af0064577 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs @@ -79,6 +79,21 @@ namespace Umbraco.Tests.TestHelpers.Entities return content; } + public static Content CreateSimpleContentWithSpecialDatabaseTypes(IContentType contentType, string name, int parentId, string decimalValue, string intValue, DateTime datetimeValue) + { + var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 }; + object obj = new + { + decimalProperty = decimalValue, + intProperty = intValue, + datetimeProperty = datetimeValue + }; + + content.PropertyValues(obj); + content.ResetDirtyProperties(false); + return content; + } + public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId) { var content = new Content("Random Content Name", parentId, contentType) { Language = "en-US", Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index e3585d0554..ef9371c203 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -412,7 +412,7 @@ namespace Umbraco.Tests.TestHelpers.Entities contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs index 1db17509e1..a4ff2d1e77 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs @@ -5,37 +5,53 @@ namespace Umbraco.Tests.TestHelpers.Entities { public static class MockedMedia { - public static IMedia CreateMediaImage(IMediaType mediaType, int parentId) - { - var media = new Media("Test Image", parentId, mediaType) - { - CreatorId = 0 - }; + public static IMedia CreateMediaImage(IMediaType mediaType, int parentId) + { + var media = new Media("Test Image", parentId, mediaType) + { + CreatorId = 0 + }; - media.SetValue(Constants.Conventions.Media.File, "/media/test-image.png"); - media.SetValue(Constants.Conventions.Media.Width, "200"); - media.SetValue(Constants.Conventions.Media.Height, "200"); - media.SetValue(Constants.Conventions.Media.Bytes, "100"); - media.SetValue(Constants.Conventions.Media.Extension, "png"); + media.SetValue(Constants.Conventions.Media.File, "/media/test-image.png"); + media.SetValue(Constants.Conventions.Media.Width, "200"); + media.SetValue(Constants.Conventions.Media.Height, "200"); + media.SetValue(Constants.Conventions.Media.Bytes, "100"); + media.SetValue(Constants.Conventions.Media.Extension, "png"); - return media; - } + return media; + } - public static IMedia CreateMediaFile(IMediaType mediaType, int parentId) - { - var media = new Media("Test File", parentId, mediaType) - { - CreatorId = 0 - }; + public static IMedia CreateMediaFile(IMediaType mediaType, int parentId) + { + var media = new Media("Test File", parentId, mediaType) + { + CreatorId = 0 + }; - media.SetValue(Constants.Conventions.Media.File, "/media/test-file.txt"); - media.SetValue(Constants.Conventions.Media.Bytes, "4"); - media.SetValue(Constants.Conventions.Media.Extension, "txt"); + media.SetValue(Constants.Conventions.Media.File, "/media/test-file.txt"); + media.SetValue(Constants.Conventions.Media.Bytes, "4"); + media.SetValue(Constants.Conventions.Media.Extension, "txt"); - return media; - } + return media; + } - public static IMedia CreateMediaFolder(IMediaType mediaType, int parentId) + public static IMedia CreateMediaImageWithCrop(IMediaType mediaType, int parentId) + { + var media = new Media("Test Image", parentId, mediaType) + { + CreatorId = 0 + }; + + media.SetValue(Constants.Conventions.Media.File, "{src: '/media/test-image.png', crops: []}"); + media.SetValue(Constants.Conventions.Media.Width, "200"); + media.SetValue(Constants.Conventions.Media.Height, "200"); + media.SetValue(Constants.Conventions.Media.Bytes, "100"); + media.SetValue(Constants.Conventions.Media.Extension, "png"); + + return media; + } + + public static IMedia CreateMediaFolder(IMediaType mediaType, int parentId) { var media = new Media("Test Folder", parentId, mediaType) { diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs new file mode 100644 index 0000000000..b60afe77c9 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs @@ -0,0 +1,66 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public class MockedPropertyTypes + { + /// + /// Returns a decimal property. + /// Requires a datatype definition Id, since this is not one of the pre-populated datatypes + /// + /// Alias of the created property type + /// Name of the created property type + /// Integer Id of a decimal datatype to use + /// Property type storing decimal value + public static PropertyType CreateDecimalProperty(string alias, string name, int dtdId) + { + return + new PropertyType("test", DataTypeDatabaseType.Decimal, alias) + { + Name = name, + Description = "Decimal property type", + Mandatory = false, + SortOrder = 4, + DataTypeDefinitionId = dtdId + }; + } + + /// + /// Returns a integer property. + /// + /// Alias of the created property type + /// Name of the created property type + /// Property type storing integer value + public static PropertyType CreateIntegerProperty(string alias, string name) + { + return + new PropertyType("test", DataTypeDatabaseType.Integer, alias) + { + Name = name, + Description = "Integer property type", + Mandatory = false, + SortOrder = 4, + DataTypeDefinitionId = -51 + }; + } + + /// + /// Returns a DateTime property. + /// + /// Alias of the created property type + /// Name of the created property type + /// Property type storing DateTime value + public static PropertyType CreateDateTimeProperty(string alias, string name) + { + return + new PropertyType("test", DataTypeDatabaseType.Date, alias) + { + Name = name, + Description = "DateTime property type", + Mandatory = false, + SortOrder = 4, + DataTypeDefinitionId = -36 + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 199f72b7f1..85d57c8886 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -1,13 +1,17 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Configuration; using System.IO; using System.Linq; using System.Reflection; +using NUnit.Framework; using SqlCE4Umbraco; using Umbraco.Core; using Umbraco.Core.IO; using umbraco.DataLayer; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Tests.TestHelpers { @@ -115,6 +119,83 @@ namespace Umbraco.Tests.TestHelpers File.Delete(umbracoSettingsFile); } + public static void AssertAllPropertyValuesAreEquals(object actual, object expected, string dateTimeFormat = null, Func sorter = null, string[] ignoreProperties = null) + { + var properties = expected.GetType().GetProperties(); + foreach (var property in properties) + { + //ignore properties that are attributed with this + var att = property.GetCustomAttribute(false); + if (att != null && att.State == EditorBrowsableState.Never) + continue; - } + if (ignoreProperties != null && ignoreProperties.Contains(property.Name)) + continue; + + var expectedValue = property.GetValue(expected, null); + var actualValue = property.GetValue(actual, null); + + if (((actualValue is string) == false) && actualValue is IEnumerable) + { + AssertListsAreEquals(property, (IEnumerable)actualValue, (IEnumerable)expectedValue, dateTimeFormat, sorter); + } + else if (dateTimeFormat.IsNullOrWhiteSpace() == false && actualValue is DateTime) + { + Assert.AreEqual(((DateTime) expectedValue).ToString(dateTimeFormat), ((DateTime)actualValue).ToString(dateTimeFormat), "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + else + { + Assert.AreEqual(expectedValue, actualValue, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + } + } + + private static void AssertListsAreEquals(PropertyInfo property, IEnumerable actualList, IEnumerable expectedList, string dateTimeFormat, Func sorter) + { + if (sorter == null) + { + //this is pretty hackerific but saves us some code to write + sorter = enumerable => + { + //semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison + var entities = enumerable.OfType().ToList(); + if (entities.Count > 0) + { + return entities.OrderBy(x => x.Id); + } + else + { + return enumerable; + } + }; + } + + var actualListEx = sorter(actualList).Cast().ToList(); + var expectedListEx = sorter(expectedList).Cast().ToList(); + + if (actualListEx.Count != expectedListEx.Count) + Assert.Fail("Collection {0}.{1} does not match. Expected IEnumerable containing {2} elements but was IEnumerable containing {3} elements", property.PropertyType.Name, property.Name, expectedListEx.Count, actualListEx.Count); + + for (int i = 0; i < actualListEx.Count; i++) + { + var actualValue = actualListEx[i]; + var expectedValue = expectedListEx[i]; + + if (((actualValue is string) == false) && actualValue is IEnumerable) + { + AssertListsAreEquals(property, (IEnumerable)actualValue, (IEnumerable)expectedValue, dateTimeFormat, sorter); + } + else if (dateTimeFormat.IsNullOrWhiteSpace() == false && actualValue is DateTime) + { + Assert.AreEqual(((DateTime)expectedValue).ToString(dateTimeFormat), ((DateTime)actualValue).ToString(dateTimeFormat), "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + else + { + Assert.AreEqual(expectedValue, actualValue, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + } + } + + + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 6c3dfb74e3..b62b44d9de 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -146,7 +146,7 @@ namespace Umbraco.Tests.TestHelpers var userService = new Lazy(() => new UserService(provider, logger, eventMessagesFactory)); var dataTypeService = new Lazy(() => new DataTypeService(provider, logger, eventMessagesFactory)); var contentService = new Lazy(() => new ContentService(provider, logger, eventMessagesFactory)); - var notificationService = new Lazy(() => new NotificationService(provider, userService.Value, contentService.Value, repositoryFactory, logger)); + var notificationService = new Lazy(() => new NotificationService(provider, userService.Value, contentService.Value, logger)); var serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, logger, eventMessagesFactory)); var memberGroupService = new Lazy(() => new MemberGroupService(provider, logger, eventMessagesFactory)); var memberService = new Lazy(() => new MemberService(provider, logger, eventMessagesFactory, memberGroupService.Value)); @@ -169,6 +169,7 @@ namespace Umbraco.Tests.TestHelpers var treeService = new Lazy(() => new ApplicationTreeService(logger, cache)); var tagService = new Lazy(() => new TagService(provider, logger, eventMessagesFactory)); var sectionService = new Lazy(() => new SectionService(userService.Value, treeService.Value, provider, cache)); + var redirectUrlService = new Lazy(() => new RedirectUrlService(provider, logger, eventMessagesFactory)); return new ServiceContext( migrationEntryService, @@ -197,7 +198,8 @@ namespace Umbraco.Tests.TestHelpers memberTypeService, memberGroupService, notificationService, - externalLoginService); + externalLoginService, + redirectUrlService); } public static IDatabaseUnitOfWorkProvider GetDatabaseUnitOfWorkProvider(ILogger logger) diff --git a/src/Umbraco.Tests/TryConvertToTests.cs b/src/Umbraco.Tests/TryConvertToTests.cs new file mode 100644 index 0000000000..7116d98038 --- /dev/null +++ b/src/Umbraco.Tests/TryConvertToTests.cs @@ -0,0 +1,105 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests +{ + [TestFixture] + public class TryConvertToTests + { + [SetUp] + public void SetUp() + { + var settings = SettingsForTests.GetDefault(); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(settings).WithDefaultConfig()); + Resolution.Freeze(); + } + + [TearDown] + public void TearDown() + { + ShortStringHelperResolver.Reset(); + } + + [Test] + public void ConvertToIntegerTest() + { + var conv = "100".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = "100.000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = "100,000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + // oops + conv = "100.001".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = 100m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = 100.000m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + // oops + conv = 100.001m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + } + + [Test] + public void ConvertToDecimalTest() + { + var conv = "100".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = "100.000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = "100,000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = "100.001".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100.001m, conv.Result); + + conv = 100m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = 100.000m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = 100.001m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100.001m, conv.Result); + + conv = 100.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + } + + [Test] + public void ConvertToDateTimeTest() + { + var conv = "2016-06-07".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(new DateTime(2016, 6, 7), conv.Result); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 175aa8612d..074ef201f5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -138,8 +138,8 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True - - ..\packages\NPoco.3.3.3\lib\net45\NPoco.dll + + ..\packages\NPoco.3.3.4\lib\net45\NPoco.dll True @@ -230,6 +230,9 @@ + + + diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index 06c9584e3a..6346dd9f5a 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -1,7 +1,10 @@ -using System.IO; +using Moq; +using System.IO; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Profiling; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; @@ -10,7 +13,7 @@ using UmbracoExamine; namespace Umbraco.Tests.UmbracoExamine { [TestFixture] - public abstract class ExamineBaseTest : BaseUmbracoConfigurationTest + public abstract class ExamineBaseTest : BaseDatabaseFactoryTest { [TestFixtureSetUp] public void InitializeFixture() @@ -21,21 +24,17 @@ namespace Umbraco.Tests.UmbracoExamine protected ProfilingLogger ProfilingLogger { get; private set; } - [SetUp] - public virtual void TestSetup() + /// + /// sets up resolvers before resolution is frozen + /// + protected override void FreezeResolution() { ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); - Resolution.Freeze(); + base.FreezeResolution(); } - [TearDown] - public virtual void TestTearDown() - { - //reset all resolvers - ResolverCollection.ResetAll(); - //reset resolution itself (though this should be taken care of by resetting any of the resolvers above) - Resolution.Reset(); - } + + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 7151a0e34e..a6731c3ed1 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -41,9 +41,8 @@ namespace Umbraco.Tests.UmbracoExamine { if (contentService == null) { - long totalRecs; - - var demoData = new ExamineDemoDataContentService(); + long longTotalRecs; + var demoData = new ExamineDemoDataContentService(); var allRecs = demoData.GetLatestContentByXPath("//*[@isDoc]") .Root @@ -69,16 +68,13 @@ namespace Umbraco.Tests.UmbracoExamine contentService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) == allRecs - - && - - x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) == - allRecs); + allRecs); } if (userService == null) { diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index a5f3c0675e..be1f907c27 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -21,7 +21,7 @@ - + diff --git a/src/Umbraco.Tests/unit-test-log4net.CI.config b/src/Umbraco.Tests/unit-test-log4net.CI.config new file mode 100644 index 0000000000..d7035032ef --- /dev/null +++ b/src/Umbraco.Tests/unit-test-log4net.CI.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index e8bc1f8300..97fc105261 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js index 52271961a7..0a69ef5340 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -1,5 +1,12 @@ angular.module("umbraco.directives") + /** + * @ngdoc directive + * @name umbraco.directives.directive:localize + * @restrict EA + * @function + * @description Localize directive + **/ .directive('localize', function ($log, localizationService) { return { restrict: 'E', @@ -40,4 +47,4 @@ angular.module("umbraco.directives") } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js new file mode 100644 index 0000000000..ba34a752ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js @@ -0,0 +1,72 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbAvatar +@restrict E +@scope + +@description +Use this directive to render an avatar. + +

Markup example

+
+	
+ + + + +
+
+ +

Controller example

+
+	(function () {
+		"use strict";
+
+		function Controller() {
+
+            var vm = this;
+
+            vm.avatar = [
+                { value: "assets/logo.png" },
+                { value: "assets/logo@2x.png" },
+                { value: "assets/logo@3x.png" }
+            ];
+
+        }
+
+		angular.module("umbraco").controller("My.Controller", Controller);
+
+	})();
+
+ +@param {string} size (attribute): The size of the avatar (xs, s, m, l, xl). +@param {string} img-src (attribute): The image source to the avatar. +@param {string} img-srcset (atribute): Reponsive support for the image source. +**/ + +(function() { + 'use strict'; + + function AvatarDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-avatar.html', + scope: { + size: "@", + imgSrc: "@", + imgSrcset: "@" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbAvatar', AvatarDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js new file mode 100644 index 0000000000..19a33a8351 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js @@ -0,0 +1,150 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbLightbox +@restrict E +@scope + +@description +

Use this directive to open a gallery in a lightbox overlay.

+ +

Markup example

+
+    
+ + + + + + +
+
+ +

Controller example

+
+    (function () {
+
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+
+            vm.images = [
+                {
+                    "source": "linkToImage"
+                },
+                {
+                    "source": "linkToImage"
+                }
+            ]
+
+            vm.openLightbox = openLightbox;
+            vm.closeLightbox = closeLightbox;
+
+            function openLightbox(itemIndex, items) {
+                vm.lightbox = {
+                    show: true,
+                    items: items,
+                    activeIndex: itemIndex
+                };
+            }
+
+            function closeLightbox() {
+                vm.lightbox.show = false;
+                vm.lightbox = null;
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {array} items Array of gallery items. +@param {callback} onClose Callback when the lightbox is closed. +@param {number} activeItemIndex Index of active item. +**/ + + +(function() { + 'use strict'; + + function LightboxDirective() { + + function link(scope, el, attr, ctrl) { + + + function activate() { + + var eventBindings = []; + + el.appendTo("body"); + + // clean up + scope.$on('$destroy', function() { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); + } + + scope.next = function() { + + var nextItemIndex = scope.activeItemIndex + 1; + + if( nextItemIndex < scope.items.length) { + scope.items[scope.activeItemIndex].active = false; + scope.items[nextItemIndex].active = true; + scope.activeItemIndex = nextItemIndex; + } + }; + + scope.prev = function() { + + var prevItemIndex = scope.activeItemIndex - 1; + + if( prevItemIndex >= 0) { + scope.items[scope.activeItemIndex].active = false; + scope.items[prevItemIndex].active = true; + scope.activeItemIndex = prevItemIndex; + } + + }; + + scope.close = function() { + if(scope.onClose) { + scope.onClose(); + } + }; + + activate(); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-lightbox.html', + scope: { + items: '=', + onClose: "=", + activeItemIndex: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbLightbox', LightboxDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbprogressbar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbprogressbar.directive.js new file mode 100644 index 0000000000..77bab9f023 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbprogressbar.directive.js @@ -0,0 +1,41 @@ + +/** +@ngdoc directive +@name umbraco.directives.directive:umbProgressBar +@restrict E +@scope + +@description +Use this directive to generate a progress bar. + +

Markup example

+
+    
+    
+
+ +@param {number} percentage (attribute): The progress in percentage. +**/ + +(function() { + 'use strict'; + + function ProgressBarDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-progress-bar.html', + scope: { + percentage: "@" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbProgressBar', ProgressBarDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/healthcheck.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/healthcheck.resource.js new file mode 100644 index 0000000000..17d2651a52 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/healthcheck.resource.js @@ -0,0 +1,76 @@ +/** + * @ngdoc service + * @name umbraco.resources.healthCheckResource + * @function + * + * @description + * Used by the health check dashboard to get checks and send requests to fix checks. + */ +(function () { + 'use strict'; + + function healthCheckResource($http, umbRequestHelper) { + + /** + * @ngdoc function + * @name umbraco.resources.healthCheckService#getAllChecks + * @methodOf umbraco.resources.healthCheckResource + * @function + * + * @description + * Called to get all available health checks + */ + function getAllChecks() { + return umbRequestHelper.resourcePromise( + $http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + "GetAllHealthChecks"), + "Failed to retrieve health checks" + ); + } + + /** + * @ngdoc function + * @name umbraco.resources.healthCheckService#getStatus + * @methodOf umbraco.resources.healthCheckResource + * @function + * + * @description + * Called to get execute a health check and return the check status + */ + function getStatus(id) { + return umbRequestHelper.resourcePromise( + $http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'GetStatus?id=' + id), + 'Failed to retrieve status for health check with ID ' + id + ); + } + + /** + * @ngdoc function + * @name umbraco.resources.healthCheckService#executeAction + * @methodOf umbraco.resources.healthCheckResource + * @function + * + * @description + * Called to execute a health check action (rectifying an issue) + */ + function executeAction(action) { + return umbRequestHelper.resourcePromise( + $http.post(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'ExecuteAction', action), + 'Failed to execute action with alias ' + action.alias + ' and healthCheckId + ' + action.healthCheckId + ); + } + + var resource = { + getAllChecks: getAllChecks, + getStatus: getStatus, + executeAction: executeAction + }; + + return resource; + + } + + + angular.module('umbraco.resources').factory('healthCheckResource', healthCheckResource); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js new file mode 100644 index 0000000000..d9307c7c28 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js @@ -0,0 +1,64 @@ +/** + * @ngdoc service + * @name umbraco.resources.ourPackageRepositoryResource + * @description handles data for package installations + **/ +function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) { + + var baseurl = "https://our.umbraco.org/webapi/packages/v1"; + + return { + + getDetails: function (packageId) { + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "/" + packageId), + 'Failed to get package details'); + }, + + getCategories: function () { + + return umbRequestHelper.resourcePromise( + $http.get(baseurl), + 'Failed to query packages'); + }, + + getPopular: function (maxResults, category) { + + if (maxResults === undefined) { + maxResults = 10; + } + if (category === undefined) { + category = ""; + } + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "?pageIndex=0&pageSize=" + maxResults + "&category=" + category + "&order=Popular"), + 'Failed to query packages'); + }, + + search: function (pageIndex, pageSize, category, query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + if (category === undefined) { + category = ""; + } + if (query === undefined) { + query = ""; + } + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query), + httpConfig, + 'Failed to query packages'); + } + + + }; +} + +angular.module('umbraco.resources').factory('ourPackageRepositoryResource', ourPackageRepositoryResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js index c1d95861eb..9dae2008e2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js @@ -7,6 +7,49 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { return { + /** + * @ngdoc method + * @name umbraco.resources.packageInstallResource#getInstalled + * @methodOf umbraco.resources.packageInstallResource + * + * @description + * Gets a list of installed packages + */ + getInstalled: function() { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "GetInstalled")), + 'Failed to get installed packages'); + }, + + validateInstalled: function (name, version) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "ValidateInstalled", { name: name, version: version })), + 'Failed to validate package ' + name); + }, + + deleteCreatedPackage: function (packageId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "DeleteCreatedPackage", { packageId: packageId })), + 'Failed to delete package ' + packageId); + }, + + uninstall: function(packageId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "Uninstall", { packageId: packageId })), + 'Failed to uninstall package'); + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/common/security/_module.js b/src/Umbraco.Web.UI.Client/src/common/security/_module.js index 15a7663d9b..c8289c754e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/_module.js @@ -1,4 +1,4 @@ -// Based loosely around work by Witold Szczerba - https://github.com/witoldsz/angular-http-auth -angular.module('umbraco.security', [ - 'umbraco.security.retryQueue', - 'umbraco.security.interceptor']); \ No newline at end of file +//TODO: This is silly and unecessary to have a separate module for this +angular.module('umbraco.security.retryQueue', []); +angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']); +angular.module('umbraco.security', ['umbraco.security.retryQueue', 'umbraco.security.interceptor']); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js index e971719398..28d91dd610 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js @@ -1,3 +1,4 @@ +//TODO: This is silly and unecessary to have a separate module for this angular.module('umbraco.security.retryQueue', []) // This is a generic retry queue for security failures. Each item is expected to expose two functions: retry and cancel. diff --git a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/security/interceptor.js rename to src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js index 2b757707ba..b80754ef67 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js @@ -1,95 +1,95 @@ -angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']) - // This http interceptor listens for authentication successes and failures - .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) { - return function(promise) { - - return promise.then( - function(originalResponse) { - // Intercept successful requests - - //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it - //expires. Then we'll update the user in the user service accordingly. - var headers = originalResponse.headers(); - if (headers["x-umb-user-seconds"]) { - // We must use $injector to get the $http service to prevent circular dependency - var userService = $injector.get('userService'); - userService.setUserTimeout(headers["x-umb-user-seconds"]); - } - - return promise; - }, function(originalResponse) { - // Intercept failed requests - - //Here we'll check if we should ignore the error, this will be based on an original header set - var headers = originalResponse.config ? originalResponse.config.headers : {}; - if (headers["x-umb-ignore-error"] === "ignore") { - //exit/ignore - return promise; - } - var filtered = _.find(requestInterceptorFilter(), function(val) { - return originalResponse.config.url.indexOf(val) > 0; - }); - if (filtered) { - return promise; - } - - //A 401 means that the user is not logged in - if (originalResponse.status === 401) { - - // The request bounced because it was not authorized - add a new request to the retry queue - promise = queue.pushRetryFn('unauthorized-server', function retryRequest() { - // We must use $injector to get the $http service to prevent circular dependency - return $injector.get('$http')(originalResponse.config); - }); - } - else if (originalResponse.status === 404) { - - //a 404 indicates that the request was not found - this could be due to a non existing url, or it could - //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it - - var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + ""; - if (originalResponse.data && originalResponse.data.ExceptionMessage) { - errMsg += "
with error:
" + originalResponse.data.ExceptionMessage + ""; - } - if (originalResponse.config.data) { - errMsg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; - } - - notifications.error( - "Request error", - errMsg); - - } - else if (originalResponse.status === 403) { - //if the status was a 403 it means the user didn't have permission to do what the request was trying to do. - //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was - //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper, - // or completely custom for services calling resources. - - //http://issues.umbraco.org/issue/U4-2749 - - //It was decided to just put these messages into the normal status messages. - - var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + ""; - if (originalResponse.config.data) { - msg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; - } - - notifications.error( - "Authorization error", - msg); - } - - return promise; - }); - }; - }]) - - .value('requestInterceptorFilter', function() { - return ["www.gravatar.com"]; - }) - - // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. - .config(['$httpProvider', function ($httpProvider) { - $httpProvider.responseInterceptors.push('securityInterceptor'); +angular.module('umbraco.security.interceptor') + // This http interceptor listens for authentication successes and failures + .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) { + return function(promise) { + + return promise.then( + function(originalResponse) { + // Intercept successful requests + + //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it + //expires. Then we'll update the user in the user service accordingly. + var headers = originalResponse.headers(); + if (headers["x-umb-user-seconds"]) { + // We must use $injector to get the $http service to prevent circular dependency + var userService = $injector.get('userService'); + userService.setUserTimeout(headers["x-umb-user-seconds"]); + } + + return promise; + }, function(originalResponse) { + // Intercept failed requests + + //Here we'll check if we should ignore the error, this will be based on an original header set + var headers = originalResponse.config ? originalResponse.config.headers : {}; + if (headers["x-umb-ignore-error"] === "ignore") { + //exit/ignore + return promise; + } + var filtered = _.find(requestInterceptorFilter(), function(val) { + return originalResponse.config.url.indexOf(val) > 0; + }); + if (filtered) { + return promise; + } + + //A 401 means that the user is not logged in + if (originalResponse.status === 401) { + + // The request bounced because it was not authorized - add a new request to the retry queue + promise = queue.pushRetryFn('unauthorized-server', function retryRequest() { + // We must use $injector to get the $http service to prevent circular dependency + return $injector.get('$http')(originalResponse.config); + }); + } + else if (originalResponse.status === 404) { + + //a 404 indicates that the request was not found - this could be due to a non existing url, or it could + //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it + + var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + ""; + if (originalResponse.data && originalResponse.data.ExceptionMessage) { + errMsg += "
with error:
" + originalResponse.data.ExceptionMessage + ""; + } + if (originalResponse.config.data) { + errMsg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; + } + + notifications.error( + "Request error", + errMsg); + + } + else if (originalResponse.status === 403) { + //if the status was a 403 it means the user didn't have permission to do what the request was trying to do. + //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was + //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper, + // or completely custom for services calling resources. + + //http://issues.umbraco.org/issue/U4-2749 + + //It was decided to just put these messages into the normal status messages. + + var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + ""; + if (originalResponse.config.data) { + msg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; + } + + notifications.error( + "Authorization error", + msg); + } + + return promise; + }); + }; + }]) + + .value('requestInterceptorFilter', function() { + return ["www.gravatar.com"]; + }) + + // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. + .config(['$httpProvider', function ($httpProvider) { + $httpProvider.responseInterceptors.push('securityInterceptor'); }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 7cfc79a083..430ed02242 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -1,3 +1,26 @@ +/** + * @ngdoc service + * @name umbraco.services.localizationService + * + * @requires $http + * @requires $q + * @requires $window + * @requires $filter + * + * @description + * Application-wide service for handling localization + * + * ##usage + * To use, simply inject the localizationService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
+ *    localizationService.localize("area_key").then(function(value){
+ *        element.html(value);
+ *    });
+ * 
+ */ + angular.module('umbraco.services') .factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) { @@ -78,7 +101,17 @@ angular.module('umbraco.services') return deferred.promise; }, - //helper to tokenize and compile a localization string + /** + * @ngdoc method + * @name umbraco.services.localizationService#tokenize + * @methodOf umbraco.services.localizationService + * + * @description + * Helper to tokenize and compile a localization string + * @param {String} value the value to tokenize + * @param {Object} scope the $scope object + * @returns {String} tokenized resource string + */ tokenize: function (value, scope) { if (value) { var localizer = value.split(':'); @@ -95,7 +128,17 @@ angular.module('umbraco.services') return value; }, - // checks the dictionary for a localized resource string + /** + * @ngdoc method + * @name umbraco.services.localizationService#localize + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for a localized resource string + * @param {String} value the area/key to localize + * @param {Array} tokens if specified this array will be sent as parameter values + * @returns {String} localized resource string + */ localize: function (value, tokens) { return service.initLocalizedResources().then(function (dic) { var val = _lookup(value, tokens, dic); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index ccdd283ea6..a3d1e5b0c6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -170,16 +170,14 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ } } - else { - //return an error object including the error message for UI - deferred.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }); + //return an error object including the error message for UI + deferred.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }); - } }); @@ -266,15 +264,14 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ } } - else { - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: 'An error occurred', - data: data, - status: status - }); - } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: 'An error occurred', + data: data, + status: status + }); + }); @@ -337,4 +334,4 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ } }; } -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); \ No newline at end of file +angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 9fbf2947af..be5e2e1232 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -1,4 +1,100 @@ /*Contains multiple services for various helper tasks */ +function versionHelper() { + + return { + + //see: https://gist.github.com/TheDistantSea/8021359 + versionCompare: function(v1, v2, options) { + var lexicographical = options && options.lexicographical, + zeroExtend = options && options.zeroExtend, + v1parts = v1.split('.'), + v2parts = v2.split('.'); + + function isValidPart(x) { + return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); + } + + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + return NaN; + } + + if (zeroExtend) { + while (v1parts.length < v2parts.length) { + v1parts.push("0"); + } + while (v2parts.length < v1parts.length) { + v2parts.push("0"); + } + } + + if (!lexicographical) { + v1parts = v1parts.map(Number); + v2parts = v2parts.map(Number); + } + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length === i) { + return 1; + } + + if (v1parts[i] === v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + return 1; + } + else { + return -1; + } + } + + if (v1parts.length !== v2parts.length) { + return -1; + } + + return 0; + } + }; +} +angular.module('umbraco.services').factory('versionHelper', versionHelper); + +function dateHelper() { + + return { + + convertToServerStringTime: function(momentLocal, serverOffsetMinutes, format) { + + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + moment() + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); + + var server = moment.utc(momentLocal).zone(formattedOffset); + return server.format(format ? format : "YYYY-MM-DD HH:mm:ss"); + }, + + convertToLocalMomentTime: function (strVal, serverOffsetMinutes) { + + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + moment() + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); + + //convert to the iso string format + var isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; + + //create a moment with the iso format which will include the offset with the correct time + // then convert it to local time + return moment.parseZone(isoFormat).local(); + } + + }; +} +angular.module('umbraco.services').factory('dateHelper', dateHelper); function packageHelper(assetsService, treeService, eventsService, $templateCache) { diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index 0d3b56991e..74eb872aa2 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -97,23 +97,18 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ function successCallback(response) { // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything if (response.data !== "null") { - $("#avatar-img").fadeTo(1000, 0, function () { - $scope.$apply(function () { - //this can be null if they time out - if ($scope.user && $scope.user.emailHash) { - var avatarBaseUrl = "https://www.gravatar.com/avatar/", - hash = $scope.user.emailHash; + if ($scope.user && $scope.user.emailHash) { + var avatarBaseUrl = "https://www.gravatar.com/avatar/"; + var hash = $scope.user.emailHash; - $scope.avatar = [ - { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } - ]; - } - }); - $("#avatar-img").fadeTo(1000, 1); - }); + $scope.avatar = [ + { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, + { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, + { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } + ]; + } } + }, function errorCallback(response) { //cannot load it from the server so we cannot do anything }); @@ -143,4 +138,4 @@ angular.module('umbraco').controller("Umbraco.MainController", MainController). config(function (tmhDynamicLocaleProvider) { //Set url for locale files tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html index 0cc7511f89..1b13768c9a 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html @@ -9,20 +9,16 @@
What type of database do you use? -
- -
+
+ +
-

Great!, no need to configure anything then, you simply click the continue button below to continue to the next step

+

Great!, no need to configure anything then, you simply click the continue button below to continue to the next step

- -
- What is the exact connectionstring we should use?
@@ -42,7 +38,7 @@
- + Enter server domain or IP
@@ -52,7 +48,7 @@
- Enter the name of the database @@ -61,49 +57,48 @@
-
- What credentials are used to access the database? -
-
- -
- - Enter the database user name -
-
-
- -
-
- -
- - Enter the database password -
-
-
- -
+ What credentials are used to access the database? +
+
+
- + + Enter the database user name
+
+ +
+
+ +
+ + Enter the database password +
+
+
+ +
+
+ +
+
-
+
- + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html index 374772451b..5242fa8554 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html @@ -3,10 +3,17 @@

Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade of its database and files, which will ensure your website is kept as fast, secure and up to date as possible.

+ +

+ To read a report of changes between your current version {{installer.current.model.currentVersion}} and this version your upgrading to {{installer.current.model.newVersion}} +

+

+ View Report +

+

Simply click continue below to be guided through the rest of the upgrade

-

diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index 2cd50fe208..7be0d23959 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -10,14 +10,14 @@
-
- -
+
+ +
-
+
Your email will be used as your login
@@ -46,17 +46,15 @@
-
- - - + Customize
diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 9d01cae4b9..3a55e87382 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -108,6 +108,11 @@ @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; @import "components/umb-iconpicker.less"; +@import "components/umb-packages.less"; +@import "components/umb-package-local-install.less"; +@import "components/umb-lightbox.less"; +@import "components/umb-avatar.less"; +@import "components/umb-progress-bar.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; @@ -115,6 +120,12 @@ @import "components/notifications/umb-notifications.less"; @import "components/umb-file-dropzone.less"; +// Utilities +@import "utilities/_flexbox.less"; +@import "utilities/_spacing.less"; +@import "utilities/_text-align.less"; +@import "utilities/_width.less"; + //page specific styles @import "pages/document-type-editor.less"; @import "pages/login.less"; @@ -126,3 +137,5 @@ @import "typeahead.less"; @import "hacks.less"; + +@import "healthcheck.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 3a5367c7c9..8c1acd97d8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -12,9 +12,9 @@ .umb-editor-sub-header.-umb-sticky-bar { box-shadow: 0 5px 0 rgba(0, 0, 0, 0.08), 0 1px 0 rgba(0, 0, 0, 0.16); transition: box-shadow 1s; - top: 100px; - transform: translate(0, 50%); - + top: 101px; /* height of header: 100px + its bottom-border: 1px */ + margin-top: 0; + margin-bottom: 0; } .umb-group-builder__property-preview .umb-editor-sub-header { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less new file mode 100644 index 0000000000..c4414c2880 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less @@ -0,0 +1,30 @@ +.umb-avatar { + border-radius: 50%; + width: 50px; + height: 50px; +} + +.umb-avatar.-xs { + width: 30px; + height: 30px; +} + +.umb-avatar.-s { + width: 40px; + height: 40px; +} + +.umb-avatar.-m { + width: 50px; + height: 50px; +} + +.umb-avatar.-l { + width: 70px; + height: 70px; +} + +.umb-avatar.-xl { + width: 100px; + height: 100px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less index d4e82a6419..6b3c46d927 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -5,7 +5,7 @@ // tall and small version - animate height .dropzone { height: 400px; - width: 100%; + width: auto; padding: 50px 0; border: 1px dashed @grayLight; text-align: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less new file mode 100644 index 0000000000..e8477396bf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less @@ -0,0 +1,75 @@ +.umb-lightbox { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 999; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.umb-lightbox__backdrop { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, 0.2); + width: 100%; + height: 100%; +} + +.umb-lightbox__close { + position: absolute; + top: 20px; + right: 20px; +} + +.umb-lightbox__images { + position: relative; + z-index: 1000; +} + +.umb-lightbox__image { + background: @white; + border-radius: 3px; + padding: 10px; + img { + max-width: 50vw; + max-height: 70vh; + } +} + +.umb-lightbox__control { + background-color: white; + width: 50px; + height: 50px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: absolute; +} + +.umb-lightbox__control.-next { + right: 20px; + top: 50%; + transform: translate(0, -50%); +} + +.umb-lightbox__control.-prev { + left: 20px; + top: 50%; + transform: translate(0, -50%); +} + +.umb-lightbox__control-icon { + color: @blue; + font-size: 20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less index b2d03147a0..6443ec47e6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less @@ -7,6 +7,8 @@ left: 50%; transform: translate(-50%, -50%); font-size: 0; + margin-left: -6px; // hack to center it + margin-top: -6px; // hack to center it } .umb-load-indicator__bubble { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less new file mode 100644 index 0000000000..2a624b1c56 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less @@ -0,0 +1,135 @@ +/* + + Install local package + +*/ + +// Helpers +.faded { + color: @grayMed; +} + + + +.umb-upload-local__dropzone { + position: relative; + width: 500px; + height: 300px; + border: 2px dashed @grayLight; + border-radius: 3px; + background: @grayLighter; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + margin-bottom: 30px; + + transition: 100ms box-shadow ease, 100ms border ease; + + &:hover, + &.drag-over { + border-color: @blue; + border-style: solid; + box-shadow: 0 3px 8px rgba(0,0,0, .1); + transition: 100ms box-shadow ease, 100ms border ease; + } +} + +.umb-upload-local__dropzone i { + display: block; + color: @grayLight; + font-size: 110px; + line-height: 1; +} + +.umb-upload-local__select-file { + font-weight: bold; + color: @blue; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} + + + +// Accept terms +.umb-accept-terms { + display: flex; + align-items: center; + + font-size: 13px; +} + +.umb-package-installer-label .label-text { + margin-left: 5px; +} + +.umb-package-installer-label input[type="radio"], +.umb-package-installer-label input[type="checkbox"] { + margin-top: 0px; +} + +.umb-package-installer-label { + display: inline-flex; + align-items: center; + + font-size: 13px; + user-select: none; +} + + + +// Info state +.umb-info-local-items { + border: 2px solid @grayLight; + border-radius: 3px; + background: @grayLighter; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + margin: 0 20px; + + width: 100%; + max-width: 540px; +} + +.umb-info-local-items a { + text-decoration: underline; + + &:hover { + text-decoration: none; + } +} + + +.umb-info-local-items .umb-package-icon { + width: 100%; + box-sizing: border-box; + min-height: 150px; + font-size: 60px; +} + +.umb-info-local-items .umb-package-icon img { + max-width: 100px; +} + +.umb-info-local-items .umb-package-info { + width: 100%; + box-sizing: border-box; + padding: 20px 40px; +} + +.umb-info-local-item { + margin-bottom: 20px; +} + +.umb-upload-local__dropzone .umb-info-local-item { + margin:20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less new file mode 100644 index 0000000000..d3af5164f1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -0,0 +1,598 @@ +.umb-packages-view-title { + font-size: 20px; + font-weight: bold; + color: @black; + margin-bottom: 30px; +} + +.umb-packages-view-wrapper { + padding: 20px 60px; +} + +.umb-packages-section { + margin-bottom: 40px; +} + +.umb-packages-search { + width: 100%; + background: @grayLighter; + border-radius: 3px; + padding: 30px; + box-sizing: border-box; +} + +.umb-packages-search input { + border-width: 2px; + border-radius: 3px; + min-height: 44px; + + padding: 4px 10px; + font-size: 16px; + border-color: #ececec; + margin-bottom: 0; + border-color: @grayLight; + + &:hover, &:focus { + border-color: @grayLight; + } +} + +.umb-packages__pagination { + display: flex; + justify-content: center; +} + +.umb-packages { + margin: 0 -10px; + display: flex; + flex-wrap: wrap; +} + +// Cards +.umb-package { + padding: 10px; + box-sizing: border-box; + flex: 0 0 100%; + max-width: 100%; +} + +@media (min-width: 768px) { + .umb-package { + flex: 0 0 50%; + max-width: 50%; + } +} + +@media (min-width: 1200px) { + .umb-package { + flex: 0 0 33.33%; + max-width: 33.33%; + } +} + +@media (min-width: 1400px) { + .umb-package { + flex: 0 0 25%; + max-width: 25%; + } +} + +@media (min-width: 1700px) { + .umb-package { + flex: 0 0 20%; + max-width: 20%; + } +} + + +@media (min-width: 1900px) { + .umb-package { + flex: 0 0 16.66%; + max-width: 16.66%; + } +} + +@media (min-width: 2200px) { + .umb-package { + flex: 0 0 14.28%; + max-width: 14.28%; + } +} + +.umb-package-link { + display: block; + flex-wrap: wrap; + flex-direction: column; + justify-content: center; + + position: relative; + box-sizing: border-box; + + height: 100%; + width: 100%; + + border: 1px solid #ececec; + border-radius: 3px; + + text-decoration: none !important; + + transition: border-color 100ms ease; + + &:hover { + border-color: @blue; + } +} + + + +// Icon +.umb-package-icon { + display: flex; + + justify-content: center; + align-items: center; + + padding-top: 10px; + padding-right: 10px; + padding-left: 10px; + padding-bottom: 10px; + + text-align: center; + background-color: white; + + border-top-right-radius: 3px; + border-top-left-radius: 3px; + + min-height: 60px; +} + +.umb-package-icon img { + max-width: 70px; + width: 70px; + height: auto; +} + + +// Info +.umb-package-info { + padding-right: 15px; + padding-bottom: 15px; + padding-left: 15px; + padding-top: 15px; + text-align: center; + background: @grayLighter; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + + border-top: 1px solid #ececec; +} + + +// Name +.umb-package-name { + font-size: 14px; + max-width: 250px; + margin-bottom: 5px; + + font-weight: bold; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + line-height: normal; + margin-left: auto; + margin-right: auto; +} + +.umb-package-description { + font-size: 11px; + color: @grayMed; + word-wrap: break-word; + line-height: 1.1rem; + + white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +// Numbers +.umb-package-numbers { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: center; + + opacity: .6; + + margin-top: 10px; +} + +.umb-package-numbers small { + padding: 0 5px; + + display: flex; + align-items: center; + justify-content: center; +} + +.umb-package-numbers i { + font-size: 14px; +} + +.umb-package-link:hover .umb-package-numbers { + opacity: 1; +} + +.umb-package-link:hover .umb-package-numbers .icon-hearts { + color: red !important; +} + +// Version +.umb-package-version { + display: inline-flex; + font-size: 11px; + font-weight: bold; + padding: 1px 5px; + background: #ececec; + border-radius: 3px; + color: black; +} + +/* umb-buttons-era */ +.umb-era-button { + display: flex; + justify-content: center; + align-items: center; + + font-size: 14px; + font-weight: bold; + + height: 38px; + line-height: 1; + + max-width: 100%; + padding: 0 18px; + + color: #484848; + background-color: #e0e0e0; + + text-decoration: none !important; + user-select: none; + + white-space: nowrap; + overflow: hidden; + + border-radius: 3px; + border: 0 none; + box-sizing: border-box; + + cursor: pointer; + + transition: background-color 80ms ease, color 80ms ease; +} + + +.umb-era-button:hover, +.umb-era-button:active { + color: #484848; + background-color: #d3d3d3; + outline: none; + text-decoration: none; +} + + +.umb-era-button:focus { + outline: none; +} + +.umb-era-button.-blue { + background: @blue; + color: white; +} + +.umb-era-button.-blue:hover { + background-color: @blueDark; +} + +.umb-era-button.-link { + padding: 0; + background: transparent; +} + +.umb-era-button.-link:hover { + background-color: transparent; + opacity: .6; +} + +.umb-era-button.-inactive { + cursor: not-allowed; + color: #BBB; + background: #EAE7E7; +} + +.umb-era-button.-inactive:hover { + color: #BBB; + background: #EAE7E7; +} + + +.umb-era-button.-full-width { + display: block; + width: 100%; +} + + +/* CATEGORIES */ + +.umb-packages-categories { + display: flex; + user-select: center; + flex-wrap: wrap; +} + +.umb-packages-category { + display: flex; + align-items: center; + flex: 1 0 auto; + justify-content: center; + max-width: 25%; + font-size: 14px; + font-weight: bold; + color: @black; + box-sizing: border-box; + justify-content: center; + border-top: 1px solid @grayLight; + border-bottom: 1px solid @grayLight; + border-right: 1px solid @grayLight; + padding: 10px 0; +} + + +@media (max-width: 768px) { + .umb-packages-category { + width: 100%; + margin-top: 0; + margin-bottom: 15px !important; + margin-left: 0 !important; + margin-right: 0 !important; + } +} + +@media (max-width: 992px) { + .umb-packages-category { + border: 1px solid @grayLight; + margin: 5px; + flex: 0 0 auto; + + text-align: center; + padding: 10px; + + max-width: 100%; + + border-radius: 3px; + } +} + +@media (min-width: 1100px) and (max-width: 1300px) { + .umb-packages-category { + border: 1px solid @grayLight; + margin: 5px; + flex: 0 0 auto; + + text-align: center; + padding: 10px; + + max-width: 100%; + + border-radius: 3px; + } +} + + +.umb-packages-category:hover, +.umb-packages-category.-active { + text-decoration: none; + color: @blue; +} + +.umb-packages-category.-first { + border-left: 1px solid @grayLight; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.umb-packages-category.-last { + border-right: 1px solid @grayLight; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +/* PACKAGE DETAILS */ + +.umb-package-details { + display: flex; +} + +a.umb-package-details__back-link { + font-weight: bold; + color: @black; +} + +.umb-package-details__back-link:hover { + color: @grayMed; + text-decoration: none; +} + + +@sidebarwidthFlax: 350px; // Width of sidebar. Ugly hack because of old version of Less + +.umb-package-details__main-content { + flex: 1 1 auto; + margin-right: 40px; + + width: ~"calc(100% - @{sidebarwidthFlax})"; // Make sure that the main content area doesn't gets affected by inline styling +} + +.umb-package-details__sidebar { + flex: 0 0 350px; +} + +.umb-package-details__section { + background: @grayLighter; + padding: 20px; + margin-bottom: 20px; + border-radius: 3px; +} + +.umb-package-details__section-title { + font-size: 17px; + font-weight: bold; + color: black; + margin-top: 0; + margin-bottom: 15px; +} + +.umb-package-details__section-description { + font-size: 12px; + line-height: 1.6em; + margin-bottom: 15px; +} + +.umb-package-details__information-item { + margin-bottom: 10px; + font-size: 13px; +} + +.umb-package-details__information-item-label { + color: black; + font-weight: bold; +} + +.umb-package-details__information-item-content { + word-break: break-word; +} + +.umb-package-details__information-item-label-2 { + font-size: 12px; + color: @grayMed; +} + +.umb-package-details__compatability { + margin-bottom: 15px; +} + +.umb-package-details__compatability-label { + margin-bottom: 3px; +} + +.umb-package-details__description { + margin-bottom: 20px; + line-height: 1.6em; +} + +.umb-package-details__description p { + margin-bottom: 20px; +} + +/* Links */ + +.umb-package-details__link { + color: #DA3287; +} + +.umb-package-details__link:hover { + color: #B32D71; + text-decoration: none; +} + +/* Owner profile */ + +.umb-package-details__owner-profile { + display: flex; + align-items: center; +} +.umb-package-details__owner-profile-avatar { + margin-right: 15px; +} + +.umb-package-details__owner-profile-name { + font-size: 15px; + color: #000000; + font-weight: bold; +} + +.umb-package-details__owner-profile-karma { + font-size: 12px; + color: @grayMed; +} + +/* gallery */ + +.umb-gallery__thumbnails { + display: flex; + flex-wrap: wrap; +} + +.umb-gallery__thumbnail { + flex: 0 1 100px; + border: 1px solid @grayLight; + border-radius: 3px; + margin: 5px; + padding: 10px; + box-sizing: border-box; + max-width: 100px; +} + +.umb-gallery__thumbnail:hover { + cursor: pointer; + border-color: @blue; +} + +/* PACKAGE LIST */ + +.umb-package-list { + display: flex; + flex-direction: column; +} + +.umb-package-list__item { + display: flex; + flex-direction: row; + background: @grayLighter; + margin-bottom: 10px; + border-radius: 3px; + padding: 20px; + align-items: center; +} + +.umb-package-list__item-icon { + flex: 0 0 50px; + margin-right: 20px; + font-size: 30px; + text-align: center; +} + +.umb-package-list__item-content { + flex: 1 1 auto; + margin-right: 20px; +} + +.umb-package-list__item-name { + font-size: 16px; + margin-bottom: 5px; + color: @black; + font-weight: bold; +} + +.umb-package-list__item-description { + font-size: 14px; + color: @grayMed; +} + +.umb-package-list__item-actions { + flex: 1 1 auto; + display: flex; + justify-content: flex-end; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-bar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-bar.less new file mode 100644 index 0000000000..e7d54bca59 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-bar.less @@ -0,0 +1,20 @@ +.umb-progress-bar { + background: @grayLight; + width: 100%; + display: block; + height: 10px; + border-radius: 10px; + box-sizing: border-box; + position: relative; + overflow: hidden; +} + +.umb-progress-bar__progress { + background: #50C878; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 100%; + border-radius: 10px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index f9dedf5902..d046ac7104 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -9,7 +9,7 @@ small.umb-detail, label small, .guiDialogTiny { - color: #b3b3b3 !important; + color: @grayMed !important; text-decoration: none; display: block; font-weight: normal; diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index fb809af015..716cb40256 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -173,3 +173,49 @@ iframe, .content-column-body { .pa-form + .pa-form { margin-top: 10px; } + + +// The below adds a default selector to all pre elements to ensure that styles are applied +// without having to add the class ".code" to the element. Styles have been created by +// combining the various declarations from the Bootstrap code.less file and fixing some mistakes +// in Bootstrap 2. +// This fixes issues with the markdown editor preview and should not cause issues with any other editor. + +// Inline code +// 1: Revert border radius to match look and feel of 7.4+ +code{ + .border-radius(@baseBorderRadius); // 1 +} + +// Blocks of code +// 1: Wrapping code is unreadable on small devices. +pre { + display: block; + padding: (@baseLineHeight - 1) / 2; + margin: 0 0 @baseLineHeight / 2; + #font > #family > .monospace; + font-size: @baseFontSize - 1; // 14px to 13px + color: @grayDark; + line-height: @baseLineHeight; + white-space: pre; // 1 + overflow-x: auto; // 1 + background-color: #f5f5f5; + border: 1px solid #ccc; // fallback for IE7-8 + border: 1px solid rgba(0,0,0,.15); + .border-radius(@baseBorderRadius); + + + // Make prettyprint styles more spaced out for readability + &.prettyprint { + margin-bottom: @baseLineHeight; + } + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + white-space: pre; // 1 + word-wrap: normal; // 1 + background-color: transparent; + border: 0; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less new file mode 100644 index 0000000000..a5f2d454f1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -0,0 +1,333 @@ +/* Vars */ + @red-orange: #FF3F34; + @sunrise: #F5D226; + @emerald: #50C878; + + +.umb-healthcheck { + display: flex; + flex-wrap: wrap; +} + + +/* Group and states */ +.umb-healthcheck-group { + display: flex; + flex-wrap: wrap; + flex-direction: column; + + background: @grayLighter; + border-radius: 3px; + + padding: 15px 10px; + box-sizing: border-box; + + text-align: center; + border: 2px solid transparent; + height: 100%; +} + +.umb-healthcheck-group:hover { + border: 2px solid @blue; + cursor: pointer; +} + +.umb-healthcheck-group__load-container { + position: relative; + height: 30px; + margin-top: 15px; + margin-bottom: 16px; +} + + +/* Title */ +.umb-healthcheck-title { + margin-bottom: 15px; + font-size: 14px; + font-weight: bold; +} + + +/* Messages */ +.umb-healthcheck-messages { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.umb-healthcheck-message { + position: relative; + background: #fff; + border-radius: 50px; + display: flex; + align-items: center; + flex: 1 1 auto; + padding: 5px 10px; + margin-bottom: 5px; + color: #000; + font-weight: bold; +} + +.umb-healthcheck-message i { + font-size: 20px; + margin-right: 5px; +} + +.umb-healthcheck-details-link { + color: @blue; +} + +.umb-healthcheck-details-link:hover { + text-decoration: none; + color: @blue; +} + + +/* Helpers */ +.align-self-center { + align-self: center; +} + + +/* umb-buttons-era */ +.umb-era-button { + display: flex; + justify-content: center; + align-items: center; + + font-size: 14px; + font-weight: bold; + + height: 38px; + line-height: 1; + + max-width: 100%; + padding: 0 18px; + + color: #484848; + background-color: #e0e0e0; + + text-decoration: none !important; + user-select: none; + + white-space: nowrap; + overflow: hidden; + + border-radius: 3px; + border: 0 none; + box-sizing: border-box; + + cursor: pointer; + + transition: background-color 80ms ease, color 80ms ease, opacity 80ms ease; +} + + +.umb-era-button:hover, +.umb-era-button:active { + color: #484848; + background-color: #d3d3d3; + outline: none; + text-decoration: none; +} + + +.umb-era-button:focus { + outline: none; +} + +.umb-era-button.-blue { + background: @blue; + color: white; +} + +.umb-era-button.-blue:hover { + background-color: @blueDark; +} + +.umb-era-button.-link { + padding: 0; + background: transparent; +} + +.umb-era-button.-link:hover { + background-color: transparent; + opacity: .6; +} + +.umb-era-button.-inactive { + cursor: not-allowed; + color: #BBB; + background: #EAE7E7; +} + +.umb-era-button.-inactive:hover { + color: #BBB; + background: #EAE7E7; +} + + +.umb-era-button.-full-width { + display: block; + width: 100%; +} + +.umb-era-button.-white { + background-color: @white; + + &:hover { + opacity: .9; + } +} + +.umb-era-button.-text-black { + color: @black; +} + + +/* Spacing for boxes */ +.umb-air { + flex: 0 0 auto; + flex-basis: 100%; + max-width: 100%; + + padding: 10px; + box-sizing: border-box; + + @media (min-width: 500px) { + flex-basis: 50%; + max-width: 50%; + } + + @media (min-width: 768px) { + flex-basis: 20%; + max-width: 20%; + } +} + + +/* DETAILS */ + +.umb-healthcheck-back-link { + font-weight: bold; + color: @black; +} + +.umb-healthcheck-group__details { + border-radius: 3px; + margin-bottom: 40px; +} + +.umb-healthcheck-group__details-group-title { + background-color: @blue; + padding: 10px 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 3px 3px 0 0; +} + +.umb-healthcheck-group__details-group-name { + font-size: 16px; + color: @white; + font-weight: bold; +} + +.umb-healthcheck-group__details-checks { + border: 2px solid @grayLight; + border-top: none; + border-radius: 0 0 3px 3px; +} + +.umb-healthcheck-group__details-check { +} + +.umb-healthcheck-group__details-check-title { + padding: 15px 20px; + background-color: @grayLighter; +} + +.umb-healthcheck-group__details-check-name { + font-size: 14px; + color: @black; + font-weight: bold; +} + +.umb-healthcheck-group__details-check-description { + font-size: 12px; + color: @grayMed; + line-height: 1.6rem; + //margin-top: 10px; +} + +.umb-healthcheck-group__details-status { + padding: 15px 0; + display: flex; + border-bottom: 2px solid @grayLighter; + position: relative; +} + +.umb-healthcheck-group__details-status-overlay { + background: @white; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + opacity: 0.9; +} + +.umb-healthcheck-group__details-status:last-child { + border-bottom: none; +} + +.umb-healthcheck-group__details-status-icon-container { + flex: 0 0 50px; + display: flex; + justify-content: center; + padding: 0 20px; +} + +.umb-healthcheck-status-icon { + font-size: 20px; + margin-top: 2px; +} + +.umb-healthcheck-status-icon.-large { + width: 70px; + height: 70px; + font-size: 30px; + background-color: @white; +} + +.umb-healthcheck-group__details-status-content { + padding: 0 20px; + flex: 1 1 auto; +} + +.umb-healthcheck-group__details-status-text { + line-height: 1.6em; +} + +.umb-healthcheck-group__details-status-actions { + display: flex; + flex-direction: column; + margin-top: 10px; +} + +.umb-healthcheck-group__details-status-action { + background-color: @grayLighter; + padding: 10px; + margin-bottom: 10px; + border-radius: 3px; +} + +.umb-healthcheck-group__details-status-action:last-child { + margin-bottom: 0; +} + +.umb-healthcheck-group__details-status-action-description { + margin-top: 10px; + font-size: 13px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index a1776bf12d..aab0c5987c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -286,14 +286,14 @@ // form styles .umb-dialog .muted, .umb-panel .muted { - color: @grayLight; + color: @grayMed; } .umb-dialog a.muted:hover, .umb-dialog a.muted:focus, .umb-panel a.muted:hover, .umb-panel a.muted:focus { - color: darken(@grayLight, 10%); + color: darken(@grayMed, 10%); } .umb-dialog .text-warning, @@ -366,17 +366,17 @@ flex: 1; } -.umb-panel-header-content.-top-position { - position: relative; - top: -12px; -} - .umb-panel-header-left-side { display: flex; flex: 1; flex-direction: row; } +.umb-panel-header-left-side.-top-position { + position: relative; + top: -12px; +} + .umb-panel-header-icon { cursor: pointer; margin-right: 5px; diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_flexbox.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_flexbox.less new file mode 100644 index 0000000000..829aba08f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_flexbox.less @@ -0,0 +1,43 @@ +/* + Flexbox +*/ + + +.flex { display: flex } + +.flex-column { flex-direction: column } +.flex-wrap { flex-wrap: wrap } + +.items-start { align-items: flex-start } +.items-end { align-items: flex-end } +.items-center { align-items: center } +.items-baseline { align-items: baseline } +.items-stretch { align-items: stretch } + +.self-start { align-self: flex-start } +.self-end { align-self: flex-end } +.self-center { align-self: center } +.self-baseline { align-self: baseline } +.self-stretch { align-self: stretch } + +.justify-start { justify-content: flex-start } +.justify-end { justify-content: flex-end } +.justify-center { justify-content: center } +.justify-between { justify-content: space-between } +.justify-around { justify-content: space-around } + +.content-start { align-content: flex-start } +.content-end { align-content: flex-end } +.content-center { align-content: center } +.content-between { align-content: space-between } +.content-around { align-content: space-around } +.content-stretch { align-content: stretch } + +/* 1. Fix for Chrome 44 bug. https://code.google.com/p/chromium/issues/detail?id=506893 */ +.flex-auto { + flex: 1 1 auto; + min-width: 0; /* 1 */ + min-height: 0; /* 1 */ +} + +.flex-none { flex: none } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less new file mode 100644 index 0000000000..64d86d7b6f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less @@ -0,0 +1,54 @@ +/* + + Spacing + +*/ + +@spacing-none: 0; +@spacing-extra-small: .25rem; +@spacing-small: .5rem; +@spacing-medium: 1rem; +@spacing-large: 2rem; +@spacing-extra-large: 4rem; +@spacing-extra-extra-large: 8rem; +@spacing-extra-extra-extra-large: 16rem; + +/* + SPACING + An eight step powers of two scale ranging from 0 to 16rem. + Namespaces are composable and thus highly grockable - check the legend below + Legend: + p = padding + m = margin + a = all + h = horizontal + v = vertical + t = top + r = right + b = bottom + l = left + 0 = none + 1 = 1st step in spacing scale + 2 = 2nd step in spacing scale + 3 = 3rd step in spacing scale + 4 = 4th step in spacing scale + 5 = 5th step in spacing scale + 6 = 6th step in spacing scale + 7 = 7th step in spacing scale +*/ + + +.m-center { + margin-left: auto; + margin-right: auto; +} + + +.mt0 { margin-top: @spacing-none; } +.mt1 { margin-top: @spacing-extra-small; } +.mt2 { margin-top: @spacing-small; } +.mt3 { margin-top: @spacing-medium; } +.mt4 { margin-top: @spacing-large; } +.mt5 { margin-top: @spacing-extra-large; } +.mt6 { margin-top: @spacing-extra-extra-large; } +.mt7 { margin-top: @spacing-extra-extra-extra-large; } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less new file mode 100644 index 0000000000..beff81d80c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less @@ -0,0 +1,7 @@ +/* + TEXT ALIGN +*/ + +.tl { text-align: left; } +.tr { text-align: right; } +.tc { text-align: center; } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_width.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_width.less new file mode 100644 index 0000000000..a9f2fd3362 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_width.less @@ -0,0 +1,26 @@ +/* + Width +*/ + + +/* Width Scale */ + +.w1 { width: 1rem; } +.w2 { width: 2rem; } +.w3 { width: 4rem; } +.w4 { width: 8rem; } +.w5 { width: 16rem; } + +.w-10 { width: 10%; } +.w-20 { width: 20%; } +.w-25 { width: 25%; } +.w-33 { width: 33%; } +.w-34 { width: 34%; } +.w-40 { width: 40%; } +.w-50 { width: 50%; } +.w-60 { width: 60%; } +.w-75 { width: 75%; } +.w-80 { width: 80%; } +.w-100 { width: 100%; } + +.w-auto { width: auto; } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 5bddbd8022..8ebb0f08ef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -14,7 +14,7 @@ @grayDarker: #222; @grayDark: #343434; @gray: #555; -@grayMed: #999; +@grayMed: #7f7f7f; @grayLight: #d9d9d9; @grayLighter: #f8f8f8; @white: #fff; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 05eb30c242..e4fde787da 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -1,9 +1,13 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js index 7b3d5698c5..53424a7d6e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js @@ -57,9 +57,11 @@ function MarkdownEditorController($scope, $element, assetsService, dialogService }); editor2.hooks.set("onPreviewRefresh", function () { - angularHelper.getCurrentForm($scope).$setDirty(); - // We must manually update the model as there is no way to hook into the markdown editor events without editing that code. - $scope.model.value = $("textarea", $element).val(); + // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library. + if ($scope.model.value !== $("textarea", $element).val()) { + angularHelper.getCurrentForm($scope).$setDirty(); + $scope.model.value = $("textarea", $element).val(); + } }); }, 200); diff --git a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js index 0f323f03aa..ede7c20538 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -24,6 +24,7 @@ module.exports = function(karma) { 'lib/../build/belle/lib/underscore/underscore-min.js', + 'lib/../build/belle/lib/moment/moment-with-locales.js', 'lib/umbraco/Extensions.js', 'lib/../build/belle/lib/rgrove-lazyload/lazyload.js', 'lib/../build/belle/lib/angular-local-storage/angular-local-storage.min.js', diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/date-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/date-helper.spec.js new file mode 100644 index 0000000000..74d2b38cfa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/date-helper.spec.js @@ -0,0 +1,53 @@ +describe('date helper tests', function () { + var dateHelper; + + beforeEach(module('umbraco.services')); + + beforeEach(inject(function ($injector) { + dateHelper = $injector.get('dateHelper'); + })); + + describe('converting to local moments', function () { + + it('converts from a positive offset', function () { + var offsetMin = 600; //+10 + var strDate = "2016-01-01 10:00:00"; + + var result = dateHelper.convertToLocalMomentTime(strDate, offsetMin); + + expect(result.format("YYYY-MM-DD HH:mm:ss Z")).toBe("2016-01-01 01:00:00 +01:00"); + }); + + it('converts from a negataive offset', function () { + var offsetMin = -420; //-7 + var strDate = "2016-01-01 10:00:00"; + + var result = dateHelper.convertToLocalMomentTime(strDate, offsetMin); + + expect(result.format("YYYY-MM-DD HH:mm:ss Z")).toBe("2016-01-01 18:00:00 +01:00"); + }); + + }); + + describe('converting to server strings', function () { + + it('converts to a positive offset', function () { + var offsetMin = 600; //+10 + var localDate = moment("2016-01-01 10:00:00"); + + var result = dateHelper.convertToServerStringTime(localDate, offsetMin, "YYYY-MM-DD HH:mm:ss Z"); + + expect(result).toBe("2016-01-01 19:00:00 +10:00"); + }); + + it('converts from a negataive offset', function () { + var offsetMin = -420; //-7 + var localDate = moment("2016-01-01 10:00:00"); + + var result = dateHelper.convertToServerStringTime(localDate, offsetMin, "YYYY-MM-DD HH:mm:ss Z"); + + expect(result).toBe("2016-01-01 02:00:00 -07:00"); + }); + + }); +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Media/Web.config b/src/Umbraco.Web.UI/Media/Web.config new file mode 100644 index 0000000000..6cbb733ea7 --- /dev/null +++ b/src/Umbraco.Web.UI/Media/Web.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index cc804e8036..2cc63ae3db 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -116,8 +116,8 @@ ..\packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll True - - ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.0-beta4\lib\net45\ClientDependency.Core.dll True @@ -136,8 +136,8 @@ ..\packages\ImageProcessor.2.3.3.0\lib\net45\ImageProcessor.dll True - - ..\packages\ImageProcessor.Web.4.5.3.0\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.Web.4.6.1.0\lib\net45\ImageProcessor.Web.dll True @@ -366,8 +366,8 @@ Umbraco.Web - - ..\packages\Umbraco.ModelsBuilder.3.0.2\lib\Umbraco.ModelsBuilder.dll + + ..\packages\Umbraco.ModelsBuilder.3.0.3\lib\Umbraco.ModelsBuilder.dll True @@ -649,6 +649,7 @@ Designer + Designer @@ -992,7 +993,6 @@ - @@ -1045,6 +1045,8 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index 0bdb4a8453..e1939d5924 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -969,6 +969,7 @@ Stylopisy Šablony XSLT soubory + Oprávnění Uživatele Typy Uživatelů Uživatelé diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 3d422d0a9b..3cb8a4ae71 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -934,6 +934,7 @@ Vennlig hilsen Umbraco roboten Maler XSLT Filer Analytics + Brukertillatelser Brukertyper typer Brukere diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config deleted file mode 100644 index 3fb38db592..0000000000 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/config/Dashboard.Release.config b/src/Umbraco.Web.UI/config/Dashboard.Release.config index 8459a88028..fe0082f009 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.Release.config +++ b/src/Umbraco.Web.UI/config/Dashboard.Release.config @@ -37,11 +37,6 @@ views/dashboard/developer/examinemanagement.html - - - views/dashboard/developer/xmldataintegrityreport.html - -
@@ -97,4 +92,15 @@
+
+ + developer + + + + views/dashboard/developer/healthcheck.html + + +
+ diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index 18adec4e90..df45708e0f 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -34,11 +34,6 @@ views/dashboard/developer/examinemanagement.html - - - views/dashboard/developer/xmldataintegrityreport.html - -
@@ -94,6 +89,16 @@ plugins/umbracocontour/formsdashboard.ascx
+
+ + developer + + + + views/dashboard/developer/healthcheck.html + + +
developer diff --git a/src/Umbraco.Web.UI/config/ExamineIndex.Release.config b/src/Umbraco.Web.UI/config/ExamineIndex.Release.config index b56a5a19e8..35ea1dd922 100644 --- a/src/Umbraco.Web.UI/config/ExamineIndex.Release.config +++ b/src/Umbraco.Web.UI/config/ExamineIndex.Release.config @@ -4,7 +4,7 @@ Umbraco examine is an extensible indexer and search engine. This configuration file can be extended to create your own index sets. Index/Search providers can be defined in the UmbracoSettings.config -More information and documentation can be found on CodePlex: http://umbracoexamine.codeplex.com +More information and documentation can be found on GitHub: https://github.com/Shazwazza/Examine/ --> diff --git a/src/Umbraco.Web.UI/config/ExamineIndex.config b/src/Umbraco.Web.UI/config/ExamineIndex.config index a853847ecb..ab962e1dac 100644 --- a/src/Umbraco.Web.UI/config/ExamineIndex.config +++ b/src/Umbraco.Web.UI/config/ExamineIndex.config @@ -4,7 +4,7 @@ Umbraco examine is an extensible indexer and search engine. This configuration file can be extended to create your own index sets. Index/Search providers can be defined in the UmbracoSettings.config -More information and documentation can be found on CodePlex: http://umbracoexamine.codeplex.com +More information and documentation can be found on GitHub: https://github.com/Shazwazza/Examine/ --> diff --git a/src/Umbraco.Web.UI/config/ExamineSettings.Release.config b/src/Umbraco.Web.UI/config/ExamineSettings.Release.config index 19b63dee7f..642785739f 100644 --- a/src/Umbraco.Web.UI/config/ExamineSettings.Release.config +++ b/src/Umbraco.Web.UI/config/ExamineSettings.Release.config @@ -4,7 +4,7 @@ Umbraco examine is an extensible indexer and search engine. This configuration file can be extended to add your own search/index providers. Index sets can be defined in the ExamineIndex.config if you're using the standard provider model. -More information and documentation can be found on CodePlex: http://umbracoexamine.codeplex.com +More information and documentation can be found on GitHub: https://github.com/Shazwazza/Examine/ --> diff --git a/src/Umbraco.Web.UI/config/ExamineSettings.config b/src/Umbraco.Web.UI/config/ExamineSettings.config index 52d38e96e6..076fb08493 100644 --- a/src/Umbraco.Web.UI/config/ExamineSettings.config +++ b/src/Umbraco.Web.UI/config/ExamineSettings.config @@ -4,7 +4,7 @@ Umbraco examine is an extensible indexer and search engine. This configuration file can be extended to add your own search/index providers. Index sets can be defined in the ExamineIndex.config if you're using the standard provider model. -More information and documentation can be found on CodePlex: http://umbracoexamine.codeplex.com +More information and documentation can be found on GitHub: https://github.com/Shazwazza/Examine/ --> diff --git a/src/Umbraco.Web.UI/config/trees.Release.config b/src/Umbraco.Web.UI/config/trees.Release.config index c5df870b4d..c82919a6c1 100644 --- a/src/Umbraco.Web.UI/config/trees.Release.config +++ b/src/Umbraco.Web.UI/config/trees.Release.config @@ -13,20 +13,18 @@ - + - - - + - + diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index e92d9adc17..990992fd86 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -16,16 +16,15 @@ + - - - + diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index be5c82d0e4..974d9f50c9 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -1,11 +1,11 @@  - + - - - + + + @@ -37,6 +37,6 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 3cbed82354..ccb51025cc 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -6,7 +6,7 @@ Tilføj domæne - Revisions spor + Revisionsspor Gennemse elementer Skift dokumenttype Kopier @@ -119,12 +119,12 @@ Alias (hvordan ville du f.eks. beskrive billedet via telefonen?) Alternative links - Klik på musen for at redigere dette punkt + Klik for at redigere dette punkt Oprettet af Oprindelig forfatter Opdateret af Oprettet den - tidspunkt for oprettelse + Tidspunkt for oprettelse Dokumenttype Redigerer Nedtagningsdato @@ -182,6 +182,8 @@ "dokument typer".]]> "media typer".]]> Dokument type uden skabelon + Ny mappe + Ny datatype Til dit website @@ -447,13 +449,13 @@ Tilføj fane - Tilføj property + Tilføj egenskab Tilføj editor Tilføj skabelon Tilføj child node Tilføj child - Rediger data type + Rediger datatype Naviger sektioner @@ -873,7 +875,7 @@ Mange hilsner fra Umbraco robotten Medlem gemt Stylesheetegenskab gemt Stylesheet gemt - Template gemt + Skabelon gemt Der er opstået en fejl under redigering Bruger gemt Brugertype gemt @@ -948,7 +950,7 @@ Mange hilsner fra Umbraco robotten Hvis indholdet af felterne skal sendes til en url, skal denne slåes til så specialtegn formateres Denne tekst vil blive brugt hvis ovenstående felter er tomme Dette felt vil blive brugt hvis ovenstående felt er tomt - Ja, med klokkeslet. Dato/tid separator: + Ja, med klokkeslæt. Dato/tid separator: Opgaver tildelt dig @@ -985,10 +987,10 @@ Mange hilsner fra Umbraco robotten Datatyper Ordbog Installerede pakker - Installér et skin' - Installér et starter kit' + Installér et skin + Installér et starterkit Sprog - Installer lokal pakke + Installér lokal pakke Makroer Medietyper Medlemmer @@ -1009,6 +1011,7 @@ Mange hilsner fra Umbraco robotten Skabeloner XSLT-filer Analytics + Brugertilladelser Bruger Typer Brugere @@ -1027,7 +1030,7 @@ Mange hilsner fra Umbraco robotten Du kan ændre dit kodeord, som giver dig adgang til Umbraco Back Office ved at udfylde formularen og klikke på knappen 'Skift dit kodeord' Indholdskanal Beskrivelsesfelt - Deaktivér User + Deaktivér bruger Dokumenttype Redaktør Uddragsfelt diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 3428323a71..37cfd78349 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -952,6 +952,7 @@ Ihr freundlicher Umbraco-Robot Vorlagen XSLT-Dateien Auswertungen + Berechtigungen Benutzertypen Benutzer diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index ebd1b19df6..7ed001cb33 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -169,6 +169,8 @@ Not a member of group(s) Child items Target + This translates to the following time on the server: + What does this mean?]]> Click to upload @@ -190,6 +192,8 @@ "document types".]]> "media types".]]> Document Type without a template + New folder + New data type Browse your website @@ -778,6 +782,15 @@ To manage your website, simply open the Umbraco back office and start adding con Package version Package version history View package website + The package and version chosen is already installed + This package cannot be installed, it requires a minimum Umbraco version of %0% + Uninstalling... + Downloading... + Importing... + Installing... + Restarting, please wait... + All done, your browser will now refresh, please wait... + Paste with full formatting (Not recommended) @@ -1213,6 +1226,7 @@ To manage your website, simply open the Umbraco back office and start adding con Templates XSLT Files Analytics + User Permissions User Types Users @@ -1275,4 +1289,106 @@ To manage your website, simply open the Umbraco back office and start adding con ...or enter a custom validation Field is mandatory + + + Value is set to the recommended value: '%0%'. + Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. + Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + + + Custom errors are set to '%0%'. + Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. + Custom errors successfully set to '%0%'. + + MacroErrors are set to '%0%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are now set to '%0%'. + + + Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. + Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). + Try Skip IIS Custom Errors successfully set to '%0%'. + + + File does not exist: '%0%'. + '%0%' in config file '%1%'.]]> + There was an error, check log for full error: %0%. + + Total XML: %0%, Total: %1% + Total XML: %0%, Total: %1% + Total XML: %0%, Total published: %1% + + Certificate validation error: '%0%' + Error pinging the URL %0% - '%1%' + You are currently %0% viewing the site using the HTTPS scheme. + The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0% + + + Enable HTTPS + Sets umbracoSSL setting to true in the appSettings of the web.config file. + The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. + + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. + + Debug compilation mode is disabled. + Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. + Debug compilation mode successfully disabled. + + Trace mode is disabled. + Trace mode is currently enabled. It is recommended to disable this setting before go live. + Trace mode successfully disabled. + + All folders have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + + All files have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + + X-Frame-Options used to control whether a site can be IFRAMed by another was found.]]> + X-Frame-Options used to control whether a site can be IFRAMed by another was not found.]]> + Set Header in Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMed by other websites. + A setting to create a header preventing IFRAMing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% + + + %0%.]]> + No headers revealing information about the website technology were found. + + In the Web.config file, system.net/mailsettings could not be found. + In the Web.config file system.net/mailsettings section, the host is not configured. + SMTP settings are configured correctly and the service is operating as expected. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + + %0%.]]> + %0%.]]> + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 9505312459..86d90f73d0 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -170,6 +170,8 @@ Not a member of group(s) Child items Target + This translates to the following time on the server: + What does this mean?]]> Click to upload @@ -192,6 +194,8 @@ "document types".]]> "media types".]]> Document Type without a template + New folder + New data type Browse your website @@ -777,6 +781,14 @@ To manage your website, simply open the Umbraco back office and start adding con Package version Package version history View package website + The package and version chosen is already installed + This package cannot be installed, it requires a minimum Umbraco version of %0% + Uninstalling... + Downloading... + Importing... + Installing... + Restarting, please wait... + All done, your browser will now refresh, please wait... Paste with full formatting (Not recommended) @@ -1218,6 +1230,7 @@ To manage your website, simply open the Umbraco back office and start adding con Templates XSLT Files Analytics + User Permissions User Types Users @@ -1280,4 +1293,106 @@ To manage your website, simply open the Umbraco back office and start adding con ...or enter a custom validation Field is mandatory + + + Value is set to the recommended value: '%0%'. + Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. + Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + + + Custom errors are set to '%0%'. + Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. + Custom errors successfully set to '%0%'. + + MacroErrors are set to '%0%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are now set to '%0%'. + + + Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. + Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). + Try Skip IIS Custom Errors successfully set to '%0%'. + + + File does not exist: '%0%'. + '%0%' in config file '%1%'.]]> + There was an error, check log for full error: %0%. + + Total XML: %0%, Total: %1% + Total XML: %0%, Total: %1% + Total XML: %0%, Total published: %1% + + Certificate validation error: '%0%' + Error pinging the URL %0% - '%1%' + You are currently %0% viewing the site using the HTTPS scheme. + The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0% + + + Enable HTTPS + Sets umbracoSSL setting to true in the appSettings of the web.config file. + The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. + + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. + + Debug compilation mode is disabled. + Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. + Debug compilation mode successfully disabled. + + Trace mode is disabled. + Trace mode is currently enabled. It is recommended to disable this setting before go live. + Trace mode successfully disabled. + + All folders have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + + All files have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + + X-Frame-Options used to control whether a site can be IFRAMed by another was found.]]> + X-Frame-Options used to control whether a site can be IFRAMed by another was not found.]]> + Set Header in Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMed by other websites. + A setting to create a header preventing IFRAMing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% + + + %0%.]]> + No headers revealing information about the website technology were found. + + In the Web.config file, system.net/mailsettings could not be found. + In the Web.config file system.net/mailsettings section, the host is not configured. + SMTP settings are configured correctly and the service is operating as expected. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + + %0%.]]> + %0%.]]> + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index 931d18c41d..ef61857eac 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -856,6 +856,7 @@ Hojas de estilo Plantillas Archivos XSLT + Permisos de Usuarios Tipos de Usuarios Usuarios diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 55d25b4eff..5d434e2750 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -990,6 +990,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Feuilles de style Modèles Fichiers XSLT + Permissions utilisateur Types d'utilisateurs Utilisateurs diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index f1a2ed358f..74df29362e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -875,6 +875,7 @@ To manage your website, simply open the Umbraco back office and start adding con גיליונות סגנון תבניות קבצי XSLT + הרשאות משתמש משתמש מקליד משתמש diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index fa125dc842..099f0a2bc5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -848,6 +848,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Fogli di stile Templates Files XSLT + Permessi Utente Tipi di Utente Utenti diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 21d85268fa..ddc3f240ad 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -1179,6 +1179,7 @@ Runwayをインストールして作られた新しいウェブサイトがど テンプレート XSLT ファイル アナリティクス + ユーザーの権限 ユーザータイプ ユーザー diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index 58d5778892..b2e30ad57c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -852,6 +852,7 @@ 스타일시트 템플릿 XSLT 파일 + 사용자권한 사용자 유형 사용자 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 5bbe4f634c..6a2d409c13 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -957,6 +957,7 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Stylesheets Sjablonen XSLT Bestanden + Gebruikersrechten Gebruiker Types Gebruikers diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index db911b07b7..f9faa9c144 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -748,6 +748,7 @@ Miłego dnia!]]> Arkusze stylów Szablony Pliki XSLT + Prawa dostępu użytkownika Typy Użytkowników Użytkowników diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index 15ca527821..e35157aa1c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -836,6 +836,7 @@ Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fech Stylesheets Modelos Arquivos XSLT + Permissões de usuário Tipos de Usuários Usuários diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 216de9f9ab..3f8342c78b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -1168,6 +1168,7 @@ Стили CSS Шаблоны Файлы XSLT + Разрешения для пользователя Типы пользователей пользователи diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index 5ed13a0bf4..d25241dd90 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -857,6 +857,7 @@ Stilmallar Sidmallar XSLT-filer + Användarrättigheter Användartyper Användare diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml new file mode 100644 index 0000000000..6452af023b --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml @@ -0,0 +1,1142 @@ + + + + Umbraco + http://kayadata.com + + + Kültür ve Hostnames + Denetim Trail + Düğüm Araçtır + Belge Türü Değiştir + Kopya + Oluştur + Paket Oluştur + Sil + Devre Dışı Bırak + Geri Dönüşümü Boşat + Belge Türü Çıkart + Belge Türü Al + Paket Ekle + Tuval Düzele + Çıkış + Taşı + Bildirimler + Genel Erişim + Yayımla + Yayından Kaldır + Yeniden Yükle + Siteleri Yeniden Yayınla + Düzelt + İzinler + Rollback + Yayın için Gönder + Çeviri Gönder + Sırala + Yayına Gönder + Çevir + Güncelle + Varsayılan Değer + + + İzin reddedildi. + Yeni Domain ekle + kaldır + Geçersiz node. + Geçersiz domain biçimi. + Domain zaten eklenmiş. + Dil + Domain + Yeni domain '%0%' oluşturuldu + Domain '%0%' silindi + Domain '%0%' zaten atanmış + Domain '%0%' güncellendi + Geçerli domain düzenle + + etki /> bir düzey yollar desteklenir
+
+ Miras Al + Kültür + + veya üst düğümleri kültürünü devralır. Ayrıca
geçerli olacaktır + Geçerli düğümün , bir etki altında çok uygulanmadığı sürece .]]> +
+ Domainler + + + Görüntüleniyor + + + Seç + Geçerli klasörü seçin + Başka birşey yapın + Kalın + Paragraf girinti iptal + Form alanı ekle + Grafik başlık ekle + Html Düzenle + Paragraf girintisi + Yatık + Ortalı + Sola Yasla + Sağa Yasla + Link ekle + Yerel bağlantı ekle + Bulet listesi + Sayısal Liste + Macro ekle + Resim ekle + Düzenleme ilişkileri + Listeye Dön + Kaydet + Kaydet ve Yayınla + Kaydet ve Onay için gönder + Önizle + Önizleme kapalı, Atanmış şablon yok + Stili seçin + Stilleri Göster + Tablo Ekle + + + Seçilen içerik için belge türünü değiştirmek için , öncelikle bu konum için geçerli türleri listesinden seçim yapın. + Ardından onaylamak ve / veya yeni akım tip özellikleri haritalama değişiklik ve Kaydet'i tıklatın . + İçerik yeniden yayımlanmıştır . + Güncel Mülkiyet + Güncel tip + Bu konum için geçerli hiçbir alternatifi olduğu gibi belge türü , değiştirilemez . Seçilen içerik öğesinin ebeveyn altında izin verilir ve mevcut tüm alt içerik öğeleri altında oluşturulacak izin eğer bir alternatif geçerli olacaktır . + Belge Türü değiştirildi + harita Özellikleri + Mülkiyet Harita + Yeni Şablon + Yeni Tip + Hiçbiri + İçerik + Yeni Belge Tipi Seçiniz + Seçilen içeriğin belge türü başarıyla [ yeni tip ] değişti ve aşağıdaki özellikleri eşleştirilmiş edilmiştir : + için + Bir veya daha fazla özellikleri olarak mülkiyet haritalama tamamlayamadı birden fazla eşleme tanımlanmış var. + Bulunduğunuz yerin için geçerli Sadece alternatif türleri görüntülenir. + + + Yayımlandı + Bu sayfa hakkında + takma ad + ( nasıl telefon üzerinden resim anlatırsınız ) + Alternatif Linkler + Bu öğeyi düzenlemek için tıklayın + Tarafından yaratıldı + orijinal yazar + tarafından güncellendi + oluşturuldu + Bu belgenin oluşturulduğu tarih / zaman + Belge Türü + kurgu + en kaldır + Bu madde yayınlanmasından sonra değiştirildi + Bu öğe yayınlanmadı + Son yayınlanan + Listede gösterilecek öğe yok. + Medya Türü + Medya öğesinin bağlantı ( lar) + Üye Grubu + rol + Üye Türü + Hiçbir tarih seçildi + Sayfa başlığı + Özellikler + Ebeveyn ' %0% ' yayımlanmamış olduğu için bu belge yayınladı ama görünür değildir + Hata : Bu ​​belge yayınlandı ancak önbellek ( iç hata ) değil + yayınlamak + Yayın Durum + en Yayınla + en yayından + temizle tarihi + Sıralama güncellenir + Düğümlerini sıralamak için, sadece düğümleri sürükleyin veya sütun başlıkları birini tıklatın . Seçerken " shift " veya " kontrol " tuşunu basılı tutarak birden fazla düğüm seçebilirsiniz + istatistik + Başlık (isteğe bağlı) + Alternatif metin (isteğe bağlı) + tip + Yayından + Son düzenleme + Bu belgenin düzenlendiği tarih / zaman + Dosya(ları) kaldırın + Belgeye Bağlantı + Grubun Üyesi(leri) + Grubun bir üyesi değil + Çocuk öğeleri + hedef + + + Yüklemek için tıklayın + Burada açılan dosyaları ... + Medya Linki + + + Nerede yeni %0% yaratmak istiyorsun + Altında bir öğe oluşturun + Bir tür ve bir başlık seçin + "belge türleri".]]> + "ortam türleri".]]> + + + Web sitenizi tarayın + - gizle + CMS açılış değilse , bu siteden pop-up izin gerekebilir + Yeni bir pencere açtı + Tekrar başlat + ziyaret + hoşgeldiniz + + + isim + konak yönetin + Bu pencereyi kapatın + Silmek istediğine emin misin + Eğer devre dışı bırakmak istediğinizden emin misiniz + %0% öğe(lerin) silinmesi onaylamak için bu kutuyu kontrol edin + Emin misiniz? + Emin misiniz? + Kes + Düzenleme Sözlük Öğe + Dil Düzenleme + Yerel bağlantı ekleme + Karakter Ekle + Grafik başlığı ekleyin + Resim ekle + Link Ekle + Bir makro eklemek için tıklayın + Tablo Ekle + Son DÜzenleme + Bağlantı + İç Bağlantı: + Yerel bağlantıları kullanırken, bağlantının önündeki "# " insert + Yeni pencerede aç + Makro ayarları + Düzenleyebileceğiniz Bu makro herhangi bir özellikleri içermiyor + Yapıştır + İzinleri düzenle + Geri dönüşüm kutusu öğeleri şimdi siliniyor. Bu işlem gerçekleşirken bu pencereyi kapatın etmeyiniz + Geri dönüşüm kutusu artık boş + Öğeleri geri dönüşüm kutusu silindiğinde, onlar sonsuza kadar gitmiş olacak + regexlib.com's webcoder şu anda üzerinde hiçbir kontrole sahip bazı sorunları, yaşanıyor. Bu rahatsızlıktan dolayı çok üzgünüz.]]> + Bir düzenli ifade arama form alanına doğrulama ekleyin. Örnek: 'E-posta', zip code 'url' + Makro kaldır + Gerekli alan + Site yeniden indekslendi + Web sitesi yenilendi önbelleği olmuştur. Tüm içerik güncel artık yayımlamak. Tüm yayınlanmamış içeriği hala yayınlanmamış olmakla birlikte + Web sitesi önbelleği yenilenir olacaktır. Yayınlanmamış içerik yayınlanmamış kalacak ise tüm yayınlanan içerik, güncellenecektir. + Sütün sayısı + Satır sayısı + + Yer tutucu kimliği ayarla senin tutucu bir kimlik ayarlayarak Çocuğunuz şablonları bu şablon içine içerik enjekte edebilir, + Bir kullanarak bu kimliği bakarak <asp:content /> element.]]> + + + Yer tutucu kimliği seçin Aşağıdaki listeden. You can sadece +       Geçerli şablonun ustadan kimliği sitesinin seçin.]]> + + Tam boyutta görmek için resmin üzerine tıklayın + öğeyi seçin + Görünüm Önbellek Öğe + + + + %0%' aşağıda
Sol taraftaki menüden 'diller' başlığı altında ek dil ekleyebilirsiniz + ]]> +
+ Kültür adı + + + Kullanıcı adınızı giriniz + Parolanızı giriniz + Ad %0%... + Adınızı girin... + Aramak için yazın... + Filtrelemek için yazın... + (Basın, her etiketinden sonra girin) etiket eklemek için yazın ... + + + Root'a izin ver + Bu Sadece İçerik Türleri İçerik ve Medya ağaçların kök düzeyinde oluşturulabilir kontrol + İzin alt düğüm çeşitleri + Belge Türü kompozisyonlar + Oluştur + Sekmesini sil + Tanım + Yeni sekme + Sekme + Küçük resim + Lüste görünümü etkinleştir + Bir sıralanabilir & göstermek için içerik öğesini yapılandırır; kendi çocuklarının aranabilir liste, çocuk ağacında gösterilen olmayacak + Liste görünümü + Etkin liste görünümü veri türü + Özel liste görünüm oluşturun + Özel liste görünümü kaldır + + + Ön değer ekle + Veritabanı veritürü + Mülkiyet editörü GUID + Mülkiyet editörü + Düğmeler + Gelişmiş ayarları etkinleştir... + Bağlam menüsünü etkinleştir + Eklenen görüntülerin maksimum varsayılan boyutu + İlgili stil + Etiketi göster + Yükseklik ve Genişlik + + + Verileriniz kaydedildi, ancak bu sayfayı yayınlamak için önce ilk düzeltmek için gereken bazı hatalar vardır: + Geçerli üyelik sağlayıcısı değişen şifreyi desteklemiyor (EnablePasswordRetrieval doğru olması gerekir) + %0% zaten var + Hatalar vardı: + Hatalar vardı: + Şifre %0% karakter uzunluğunda en az olması ve en az %1% non-alfa sayısal karakter (ler) içermelidir + %0% bir tamsayı olmalıdır + %1% sekmesinde %0% alan zorunludur + %0% zorunlu bir alandır + %0% - %1% bir doğru biçimde değil + %0% Bir doğru biçimde değil + + + Belirtilen dosya türü yönetici tarafından izin verilmeyen olmuştur + NOT! CodeMirror yapılandırma tarafından etkin olsa bile yeterince kararlı değil, çünkü Internet Explorer'da devre dışı bırakılır. + Yeni özellik tipine takma adını ve hem de doldurunuz! + Belirli bir dosya veya klasör için okuma / yazma erişimi olan bir sorun var + Error loading Partial View script (Dosya: %0%) + Error loading userControl '%0%' + Error loading customControl (Assembly: %0%, Type: '%1%') + Error loading MacroEngine script (Dosya: %0%) + "Error parsing XSLT file: %0% + "Error reading XSLT file: %0% + Lütfen bir başlık girin + Lütfen bir tür seçin + Orijinal boyutundan daha resmi büyütmek üzereyiz. Devam etmek istediğinizden emin misiniz? + Python komut dosyası hatası + O hatayı içerdiği için python komut dosyası, kaydedilmemiş (ler) + Silinen düğüm başlatın, lütfen yöneticinize başvurun + Tarzı değiştirmeden önce içerik işaretleyiniz + Henüz aktif stilleri + Birleştirmek istediğiniz iki hücre solundaki imleci Lütfen + Sen birleştirilmiş henüz bir hücreyi bölemezsiniz. + XSLT kaynak hatae + O hatayı içerdiği XSLT, kaydedilmemiş (ler)) + Bu özellik için kullanılan veri türüne sahip bir yapılandırma hatası var, veri türünü kontrol edin + + + Hakkında + Eylem + Eylemler + Ekle + Takma ad + Emin misiniz? + Sınır + tarafında + İptal + hücre marjı + Seçim + Kapat + Pencereyi kapat + Açıklama + Onayla + oranları sınırlamak + Devam et + Kopyala + Oluştur + Veritabanı + Tarih + Standart + Sil + Silindi + Siliniyor... + Dizayn + Boyutlar + Aşağı + İndir + Düzenle + Düzenlendi + Elemanları + E-Posta + Hata + Bul + Yükseklik + Yardım + İkon + İthalat + İç Marj + Ekle + Kur + Satır Uzunluğu + Dil + Düzen + Yükleniyor + Kilitli + Giriş yap + Oturum Kapat + Çıkış yap + Makro + Taşı + Daha + Ad + Yeni + Sonraki + Hayır + arasında + TAMAM + + veya + Parola + Yol + Yer tutucu ID + Bir dakika lütfen... + Önceki + Özellikler + Form verilerini almak için e-posta + Geridönüşüm kutusu + Kalan + Adını Değiştir + Yenile + Gerekli + Tekrar dene + İzinler + Arama + Sunucu + Göster + Gönder sayfasını göster + Boyut + Sırala + Tip + Aramak için yazın... + Yukarı + Güncelle + Yükselt + Yükle + URL + Kullanıcı + Kullanıcı adı + Değer + Görünüm + Hoşgeldiniz... + Genişlik + Evet + Klasör + Arama Sonuçları + + + Arka plan rengi + Kalın + Metin Rengi + Yazı + Metin + + + Sayfa + + + Yükleyici veritabanına bağlanamıyor. + Web.config dosyasını kaydedilemedi. El bağlantı dizesini değiştirin lütfen. + Web.config dosyasını kaydedilemedi. El bağlantı dizesini değiştirin lütfen.... + Veritabanı yapılandırması + + Kurulum için dğümeye basın %0% veritabanı + ]]> + + Sonraki Devam için.]]> + + Veritabanı bulunamadı! "Web.config" nin "bağlantı dizesinde" bilgi dosyası doğru olup olmadığını kontrol edin.

+

Devam etmek için, (Visual Studio veya sevdiğiniz metin editörü kullanarak) "web.config" dosyasını düzenlemek lütfen, altına gidin "UmbracoDbDSN" adlı anahtarı veritabanınız için bağlantı dizesini eklemek ve dosyayı kaydedin.

+

+ Tekrar dene. +
+ + Burada düzenleme web.config Hakkında Daha Fazla Bilgi.

]]> +
+ + + Gerekirse ISS'nize irtibata geçiniz. + Eğer yerel makine veya sunucu üzerinde yükleme ediyorsanız, sistem yöneticinizden bilgi gerekebilir.]]> + + + + CMS %0% için veritabanını yükseltme için yükseltme düğmesine basın +

+

+ Merak etmeyin - hiçbir içerik silinmeyecek ve her şey sonradan çalışmaya devam edecektir! +

+ ]]> +
+ + Sonraki işlem. ]]> + + next to continue the configuration wizard]]> + The Default users' password needs to be changed!]]> + The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> + The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> + The password is changed! + + + Umbraco creates a default user with a login ('admin') and password ('default'). It's important that the password is + changed to something unique. +

+

+ This step will check the default user's password and suggest if it needs to be changed. +

+ ]]> +
+ Get a great start, watch our introduction videos + By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. + Not installed yet. + Affected files and folders + More information on setting up permissions for Umbraco here + You need to grant ASP.NET modify permissions to the following files/folders + + Your permission settings are almost perfect!

+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]> +
+ How to Resolve + Click here to read the text version + video tutorial on setting up folder permissions for Umbraco or read the text version.]]> + + Your permission settings might be an issue! +

+ You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]> +
+ + Your permission settings are not ready for Umbraco! +

+ In order to run Umbraco, you'll need to update your permission settings.]]> +
+ + Your permission settings are perfect!

+ You are ready to run Umbraco and install packages!]]> +
+ Resolving folder issue + Follow this link for more information on problems with ASP.NET and creating folders + Setting up folder permissions + + + + Baştan başlamak istiyorum + + learn how) + You can still choose to install Runway later on. Please go to the Developer section and choose Packages. + ]]> + + You've just set up a clean Umbraco platform. What do you want to do next? + Runway is installed + + + This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules + ]]> + + Only recommended for experienced users + I want to start with a simple website + + + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, + Runway offers an easy foundation based on best practices to get you started faster than ever. + If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages. +

+ + Included with Runway: Home page, Getting Started page, Installing Modules page.
+ Optional Modules: Top Navigation, Sitemap, Contact, Gallery. +
+ ]]> +
+ What is Runway + Step 1/5 Accept license + Step 2/5: Database configuration + Step 3/5: Validating File Permissions + Step 4/5: Check Umbraco security + Step 5/5: Umbraco is ready to get you started + Thank you for choosing Umbraco + + Browse your new site +You installed Runway, so why not see how your new website looks.]]> + + + Further help and information +Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> + + Umbraco %0% is installed and ready for use + + /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> + + + started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, +you can find plenty of resources on our getting started pages.]]> +
+ + Launch Umbraco +To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> + + Connection to database failed. + Umbraco Version 3 + Umbraco Version 4 + Watch + + Umbraco %0% for a fresh install or upgrading from version 3.0. +

+ Press "next" to start the wizard.]]> +
+ + + Kültür Kodu + Kültür Adı + + + Sen boşta oldum ve çıkış otomatik olarak gerçekleşecek + İşinizi kaydetmek için şimdi Yenile + + + Pazar + Pazartesi + Salı + Çarşamba + Perşembe + Cuma + İçerik Yönetim Sistemi + Giriş Yapın + Oturum zaman aşımına uğradı + © 2015 - %0%
kayadata.com

]]>
+ + + Gösterge Paneli + Bölümler + İçerik + + + Choose page above... + %0% has been copied to %1% + Select where the document %0% should be copied to below + %0% has been moved to %1% + Select where the document %0% should be moved to below + has been selected as the root of your new content, click 'ok' below. + No node selected yet, please select a node in the list above before clicking 'ok' + The current node is not allowed under the chosen node because of its type + The current node cannot be moved to one of its subpages + The current node cannot exist at the root + The action isn't allowed since you have insufficient permissions on 1 or more child documents. + Relate copied items to original + + + Edit your notification for %0% + + + + + Hi %0%

+ +

This is an automated mail to inform you that the task '%1%' + has been performed on the page '%2%' + by the user '%3%' +

+ +

+

Update summary:

+ + %6% +
+

+ + + +

Have a nice day!

+ Cheers from the Umbraco robot +

]]> +
+ [%0%] Notification about %1% performed on %2% + Notifications + + + + + button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. + ]]> + + Author + Demonstration + Documentation + Package meta data + Package name + Package doesn't contain any items + +
+ You can safely remove this from the system by clicking "uninstall package" below.]]> +
+ No upgrades available + Package options + Package readme + Package repository + Confirm uninstall + Package was uninstalled + The package was successfully uninstalled + Uninstall package + + + Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, + so uninstall with caution. If in doubt, contact the package author.]]> + + Download update from the repository + Upgrade package + Upgrade instructions + There's an upgrade available for this package. You can download it directly from the Umbraco package repository. + Package version + Package version history + View package website + + + Paste with full formatting (Not recommended) + The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. + Paste as raw text without any formatting at all + Paste, but remove formatting (Recommended) + + + Role based protection + using Umbraco's member groups.]]> + role-based authentication.]]> + Error Page + Used when people are logged on, but do not have access + Choose how to restrict access to this page + %0% is now protected + Protection removed from %0% + Login Page + Choose the page that contains the login form + Remove Protection + Select the pages that contain login form and error messages + Pick the roles who have access to this page + Set the login and password for this page + Single user protection + If you just want to setup simple protection using a single login and password + + + + + + + + + + + + + + + Include unpublished child pages + Publishing in progress - please wait... + %0% out of %1% pages have been published... + %0% has been published + %0% and subpages have been published + Publish %0% and all its subpages + + ok to publish %0% and thereby making its content publicly available.

+ You can publish this page and all it's sub-pages by checking publish all children below. + ]]> +
+ + + You have not configured any approved colours + + + enter external link + choose internal page + Caption + Link + New window + Enter a new caption + Enter the link + + + Reset + + + Current version + Red text will not be shown in the selected version. , green means added]]> + Document has been rolled back + This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view + Rollback to + Select version + View + + + Düzenleme komut dosyası + + + Kapıcı + İçerik + Kurya + Geliştirici + CMS Yapılandırma Sihirbazı + Medya + Üyeler + Haber Bültenleri + Ayarlar + İstatistik + Çeviri + Kullanıcılar + Yardım + Formlar + Analytics + + + git + Help topics for + Video chapters for + Kayadata + + + Varsayılan şablonu + Sözlük Key + To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) + New Tab Title + Node type + Type + Stylesheet + Script + Stylesheet property + Tab + Tab Title + Tabs + Master Content Type enabled + This Content Type uses + as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself + No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Master Document Type + Create matching template + + + Sorting complete. + Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items +
Do not close this window during sorting]]>
+ + + Hata + Kullanıcı izniniz yeterli olmadığı için, işleminiz gerçekleştirilmedi. + İptal Edildi + İşleminiz 3.Parti yazılım tarafından iptal edildi. + Sayfa yayınlama 3.Parti yazılım tarafından iptal edildi. + Property type already exists + Property type created + DataType: %1%]]> + Propertytype deleted + Document Type saved + Tab created + Tab deleted + Tab with id: %0% deleted + Stylesheet not saved + Stylesheet saved + Stylesheet saved without any errors + Datatype saved + Dictionary item saved + Publishing failed because the parent page isn't published + Content published + and visible at the website + Content saved + Remember to publish to make changes visible + Sent For Approval + Changes have been sent for approval + Media saved + Media saved without any errors + Member saved + Stylesheet Property Saved + Stylesheet saved + Template saved + Error saving user (check log) + User Saved + User type saved + File not saved + file could not be saved. Please check file permissions + File saved + File saved without any errors + Language saved + Python script not saved + Python script could not be saved due to error + Python script saved + No errors in python script + Template not saved + Please make sure that you do not have 2 templates with the same alias + Template saved + Template saved without any errors! + XSLT not saved + XSLT contained an error + XSLT could not be saved, check file permissions + XSLT saved + No errors in XSLT + Content unpublished + Partial view saved + Partial view saved without any errors! + Partial view not saved + An error occurred saving the file. + Script view saved + Script view saved without any errors! + Script view not saved + An error occurred saving the file. + An error occurred saving the file. + + + Uses CSS syntax ex: h1, .redHeader, .blueTex + Edit stylesheet + Edit stylesheet property + Name to identify the style property in the rich text editor + Preview + Styles + + + Edit template + Insert content area + Insert content area placeholder + Insert dictionary item + Insert Macro + Insert Umbraco page field + Master template + Quick Guide to Umbraco template tags + Template + + + Insert control + Choose a layout for the page + below and add your first element]]> + + Click to embed + Click to insert image + Image caption... + Write here... + Grid layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add grid layout + Adjust the layout by setting column widths and adding additional sections + + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + + + Alternative field + Alternative Text + Casing + Encoding + Choose field + Convert line breaks + Replaces line breaks with html-tag &lt;br&gt; + Custom Fields + Yes, Date only + Format as date + HTML encode + Will replace special characters by their HTML equivalent. + Will be inserted after the field value + Will be inserted before the field value + Lowercase + None + Insert after field + Insert before field + Recursive + Remove Paragraph tags + Will remove any &lt;P&gt; in the beginning and end of the text + Standard Fields + Uppercase + URL encode + Will format special characters in URLs + Will only be used when the field values above are empty + This field will only be used if the primary field is empty + Yes, with time. Separator: + + + Tasks assigned to you + + assigned to you. To see a detailed view including comments, click on "Details" or just the page name. + You can also download the page as XML directly by clicking the "Download Xml" link.
+ To close a translation task, please go to the Details view and click the "Close" button. + ]]> +
+ close task + Translation details + Download all translation tasks as XML + Download XML + Download XML DTD + Fields + Include subpages + + + + [%0%] Translation task for %1% + No translator users found. Please create a translator user before you start sending content to translation + Tasks created by you + + created by you. To see a detailed view including comments, + click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link. + To close a translation task, please go to the Details view and click the "Close" button. + ]]> + + The page '%0%' has been send to translation + Send the page '%0%' to translation + Assigned by + Task opened + Total words + Translate to + Translation completed. + You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. + Translation failed, the XML file might be corrupt + Translation options + Translator + Upload translation XML + + + Cache Browser + Recycle Bin + Created packages + Data Types + Dictionary + Installed packages + Install skin + Install starter kit + Languages + Install local package + Macros + Media Types + Members + Member Groups + Roles + Member Types + Document Types + Packages + Packages + Python Files + Install from repository + Install Runway + Runway modules + Scripting Files + Scripts + Stylesheets + Templates + XSLT Files + Analytics + + + New update ready + %0% is ready, click here for download + No connection to server + Error checking for update. Please review trace-stack for further information + + + Yönetici + Kategori alanı + Şifreni değiştir + Şifreni değiştir + Yeni şifreyi onaylad + Aşağıdaki formu doldurarak CMS Geri Office'i erişmek için parolanızı değiştirmeniz ve ' Şifre Değiştir ' düğmesine tıklayabilirsiniz + İçerik Kanal + Açıklama alanı + Kullanıcıyı Devre Dışı Bırak + Belge Türü + editör + Alıntı alan + Dil + Kullanıcı adı + Medya Kütüphane düğüm başlatın + Bölümler + CMS Erişim devre dışı bırakma + Parola + Şifre sıfırlamak + Şifreniz değiştirildi! + Yeni parolayı onaylayın Lütfen + Yeni şifrenizi girin + Yeni şifre boş olamaz ! + Mevcut Şifre + Geçersiz Geçerli şifre + Yeni şifre ile teyit şifre arasında bir fark yoktu. Lütfen tekrar deneyin! + Teyit şifre yeni bir şifre eşleşmiyor ! + Alt düğümü izinlerini değiştirin + Şu anda sayfaları için izinleri değiştiriyorsunuz: + Onların izinlerini değiştirmek için sayfaları seçin + Tüm çocukların ara + içerikte Düğüm başlat + İsim + Kullanıcı izinleri + Kullanıcı türü + Kullanıcı tipleri + Yazar + Translator + Change + Profiliniz + Son tarih + Oturum sona eriyor + +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index d703dee135..f19e5e2cf9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -926,6 +926,7 @@ 样式表 模板 XSLT文件 + 用户权限 用户类型 Users diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/editPackage.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/editPackage.aspx index edea60c62b..87faed4e43 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/editPackage.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/editPackage.aspx @@ -35,13 +35,28 @@ * + + + + + + + + + * + Invalid version number (eg. 7.5.0) + + + - + * @@ -52,6 +67,7 @@ ControlToValidate="packageAuthorUrl">* + diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 6d91008f79..8145d121cc 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -371,7 +371,7 @@ xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='HtmlAgilityPack']])" /> - + - + @@ -228,7 +228,7 @@ - + @@ -368,7 +368,7 @@ - + diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 8fe00c6061..997767ab4c 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -29,6 +29,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Plugins; using Umbraco.Core.Security; +using Umbraco.Web.HealthCheck; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; @@ -52,7 +53,7 @@ namespace Umbraco.Web.Editors /// /// A controller to render out the default back office view and JS results /// - [UmbracoUseHttps] + [UmbracoRequireHttps] [DisableClientCache] public class BackOfficeController : UmbracoController { @@ -358,6 +359,10 @@ namespace Umbraco.Web.Editors { "xmlDataIntegrityBaseUrl", Url.GetUmbracoApiServiceBaseUrl( controller => controller.CheckContentXmlTable()) + }, + { + "healthCheckBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllHealthChecks()) } } }, @@ -696,6 +701,10 @@ namespace Umbraco.Web.Editors app.Add("cdf", ClientDependencySettings.Instance.Version); //useful for dealing with virtual paths on the client side when hosted in virtual directories especially app.Add("applicationPath", HttpContext.Request.ApplicationPath.EnsureEndsWith('/')); + + //add the server's GMT time offset in minutes + app.Add("serverTimeOffset", Convert.ToInt32(DateTimeOffset.Now.Offset.TotalMinutes)); + return app; } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 23641b1399..5733427269 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -468,7 +468,8 @@ namespace Umbraco.Web.Editors public HttpResponseMessage EmptyRecycleBin() { Services.ContentService.EmptyRecycleBin(); - return Request.CreateResponse(HttpStatusCode.OK); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); } /// diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index bf9f2056b3..495217cec2 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -97,10 +97,11 @@ namespace Umbraco.Web.Editors var d = new Dictionary(); //add the files if any var files = contentItem.UploadedFiles.Where(x => x.PropertyAlias == p.Alias).ToArray(); - if (files.Any()) + if (files.Length > 0) { d.Add("files", files); } + var data = new ContentPropertyData(p.Value, p.PreValues, d); //get the deserialized value from the property editor @@ -113,7 +114,7 @@ namespace Umbraco.Web.Editors var valueEditor = p.PropertyEditor.ValueEditor; //don't persist any bound value if the editor is readonly if (valueEditor.IsReadOnly == false) - { + { var propVal = p.PropertyEditor.ValueEditor.ConvertEditorToDb(data, dboProperty.Value); var supportTagsAttribute = TagExtractor.GetAttribute(p.PropertyEditor); if (supportTagsAttribute != null) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 824fdbfeed..3c7e151473 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -376,7 +376,8 @@ namespace Umbraco.Web.Editors public HttpResponseMessage EmptyRecycleBin() { Services.MediaService.EmptyRecycleBin(); - return Request.CreateResponse(HttpStatusCode.OK); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); } /// @@ -605,25 +606,7 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK, tempFiles); } - - /// - /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the - /// temporary files that were created. - /// - [DataContract] - private class PostedFiles : IHaveUploadedFiles, INotificationModel - { - public PostedFiles() - { - UploadedFiles = new List(); - Notifications = new List(); - } - public List UploadedFiles { get; private set; } - - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } - + /// /// Ensures the item can be moved/copied to the new location /// diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index c92421d911..5fb8e8a683 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -4,29 +4,445 @@ using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; +using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Web.Http; +using System.Web.UI.WebControls; using System.Xml; using System.Xml.Linq; +using umbraco; using umbraco.BusinessLogic; +using umbraco.cms.businesslogic.packager; using umbraco.cms.businesslogic.packager.repositories; +using umbraco.cms.businesslogic.web; +using umbraco.cms.presentation.Trees; +using umbraco.presentation.developer.packages; +using umbraco.webservices; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.UI; +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; +using File = System.IO.File; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; +using Settings = umbraco.cms.businesslogic.packager.Settings; +using Version = System.Version; namespace Umbraco.Web.Editors { + /// + /// A controller used for installing packages and managing all of the data in the packages section in the back office + /// [PluginController("UmbracoApi")] [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)] public class PackageInstallController : UmbracoAuthorizedJsonController { + /// + /// This checks if this package & version is alraedy installed + /// + /// + /// + /// + [HttpPost] + public IHttpActionResult ValidateInstalled(string name, string version) + { + var validate = ValidateInstalledInternal(name, version); + if (validate == false) + return BadRequest(); + return Ok(); + } + [HttpPost] + public IHttpActionResult Uninstall(int packageId) + { + var pack = InstalledPackage.GetById(packageId); + if (pack == null) return NotFound(); + + PerformUninstall(pack); + + //now get all other packages by this name since we'll uninstall all versions + foreach (var installed in InstalledPackage.GetAllInstalledPackages() + .Where(x => x.Data.Name == pack.Data.Name && x.Data.Id != pack.Data.Id)) + { + //remove from teh xml + installed.Delete(Security.GetUserId()); + } + + return Ok(); + } + + /// + /// SORRY :( I didn't have time to put this in a service somewhere - the old packager did this all manually too + /// + /// + protected void PerformUninstall(InstalledPackage pack) + { + if (pack == null) throw new ArgumentNullException("pack"); + + var refreshCache = false; + + //Uninstall templates + foreach (var item in pack.Data.Templates.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var found = Services.FileService.GetTemplate(nId); + if (found != null) + { + ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId()); + } + pack.Data.Templates.Remove(nId.ToString()); + } + + //Uninstall macros + foreach (var item in pack.Data.Macros.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var macro = Services.MacroService.GetById(nId); + if (macro != null) + { + Services.MacroService.Delete(macro); + } + pack.Data.Macros.Remove(nId.ToString()); + } + + //Remove Document Types + var contentTypes = new List(); + var contentTypeService = Services.ContentTypeService; + foreach (var item in pack.Data.Documenttypes.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var contentType = contentTypeService.Get(nId); + if (contentType == null) continue; + contentTypes.Add(contentType); + pack.Data.Documenttypes.Remove(nId.ToString(CultureInfo.InvariantCulture)); + // refresh content cache when document types are removed + refreshCache = true; + } + + //Order the DocumentTypes before removing them + if (contentTypes.Any()) + { + var orderedTypes = from contentType in contentTypes + orderby contentType.ParentId descending, contentType.Id descending + select contentType; + foreach (var contentType in orderedTypes) + { + contentTypeService.Delete(contentType); + } + } + + //Remove Dictionary items + foreach (var item in pack.Data.DictionaryItems.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var di = Services.LocalizationService.GetDictionaryItemById(nId); + if (di != null) + { + Services.LocalizationService.Delete(di); + } + pack.Data.DictionaryItems.Remove(nId.ToString()); + } + + //Remove Data types + foreach (var item in pack.Data.DataTypes.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var dtd = Services.DataTypeService.GetDataTypeDefinitionById(nId); + if (dtd != null) + { + Services.DataTypeService.Delete(dtd); + } + pack.Data.DataTypes.Remove(nId.ToString()); + } + + pack.Save(); + + // uninstall actions + //TODO: We should probably report errors to the UI!! + // This never happened before though, but we should do something now + if (pack.Data.Actions.IsNullOrWhiteSpace() == false) + { + try + { + var actionsXml = new XmlDocument(); + actionsXml.LoadXml("" + pack.Data.Actions + ""); + + LogHelper.Debug("executing undo actions: {0}", () => actionsXml.OuterXml); + + foreach (XmlNode n in actionsXml.DocumentElement.SelectNodes("//Action")) + { + try + { + global::umbraco.cms.businesslogic.packager.PackageAction + .UndoPackageAction(pack.Data.Name, n.Attributes["alias"].Value, n); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred running undo actions", ex); + } + } + } + catch (Exception ex) + { + LogHelper.Error("An error occurred running undo actions", ex); + } + } + + //moved remove of files here so custom package actions can still undo + //Remove files + foreach (var item in pack.Data.Files.ToArray()) + { + //here we need to try to find the file in question as most packages does not support the tilde char + var file = IOHelper.FindFile(item); + if (file != null) + { + var filePath = IOHelper.MapPath(file); + if (File.Exists(filePath)) + { + File.Delete(filePath); + + } + } + pack.Data.Files.Remove(file); + } + pack.Save(); + pack.Delete(Security.GetUserId()); + + //TODO: Legacy - probably not needed + if (refreshCache) + { + library.RefreshContent(); + } + TreeDefinitionCollection.Instance.ReRegisterTrees(); + _Legacy.Actions.Action.ReRegisterActionsAndHandlers(); + } + + /// + /// Returns all installed packages - only shows their latest versions + /// + /// + public IEnumerable GetInstalled() + { + return InstalledPackage.GetAllInstalledPackages() + .GroupBy( + //group by name + x => x.Data.Name, + //select the package with a parsed version + pck => + { + Version pckVersion; + return Version.TryParse(pck.Data.Version, out pckVersion) + ? new {package = pck, version = pckVersion} + : new {package = pck, version = new Version(0, 0, 0)}; + }) + .Select(grouping => + { + //get the max version for the package + var maxVersion = grouping.Max(x => x.version); + //only return the first package with this version + return grouping.First(x => x.version == maxVersion).package; + }) + .Select(pack => new InstalledPackageModel + { + Name = pack.Data.Name, + Id = pack.Data.Id, + Author = pack.Data.Author, + Version = pack.Data.Version, + Url = pack.Data.Url, + License = pack.Data.License, + LicenseUrl = pack.Data.LicenseUrl, + Files = pack.Data.Files, + IconUrl = pack.Data.IconUrl + }) + .ToList(); + } + + /// + /// Deletes a created package + /// + /// + /// + [HttpPost] + [HttpDelete] + public IHttpActionResult DeleteCreatedPackage(int packageId) + { + var package = CreatedPackage.GetById(packageId); + if (package == null) + return NotFound(); + + package.Delete(); + + return Ok(); + } + + private void PopulateFromPackageData(LocalPackageInstallModel model) + { + var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); + //this will load in all the metadata too + var tempDir = ins.Import(model.ZipFilePath, false); + + model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempDir); + model.Name = ins.Name; + model.Author = ins.Author; + model.AuthorUrl = ins.AuthorUrl; + model.IconUrl = ins.IconUrl; + model.License = ins.License; + model.LicenseUrl = ins.LicenseUrl; + model.ReadMe = ins.ReadMe; + model.ConflictingMacroAliases = ins.ConflictingMacroAliases; + model.ConflictingStyleSheetNames = ins.ConflictingStyleSheetNames; + model.ConflictingTemplateAliases = ins.ConflictingTemplateAliases; + model.ContainsBinaryFileErrors = ins.ContainsBinaryFileErrors; + model.ContainsMacroConflict = ins.ContainsMacroConflict; + model.ContainsStyleSheetConflicts = ins.ContainsStyleSheeConflicts; + model.ContainsTemplateConflicts = ins.ContainsTemplateConflicts; + model.ContainsUnsecureFiles = ins.ContainsUnsecureFiles; + model.Url = ins.Url; + model.Version = ins.Version; + + model.UmbracoVersion = ins.RequirementsType == RequirementsType.Strict + ? string.Format("{0}.{1}.{2}", ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch) + : string.Empty; + + //now we need to check for version comparison + model.IsCompatible = true; + if (ins.RequirementsType == RequirementsType.Strict) + { + var packageMinVersion = new System.Version(ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch); + if (UmbracoVersion.Current < packageMinVersion) + { + model.IsCompatible = false; + } + } + } + + private bool ValidateInstalledInternal(string name, string version) + { + var allInstalled = InstalledPackage.GetAllInstalledPackages(); + var found = allInstalled.FirstOrDefault(x => + { + if (x.Data.Name != name) return false; + //match the exact version + if (x.Data.Version == version) + { + return true; + } + //now try to compare the versions + Version installed; + Version selected; + if (Version.TryParse(x.Data.Version, out installed) && Version.TryParse(version, out selected)) + { + if (installed >= selected) return true; + } + return false; + }); + if (found != null) + { + //this package is already installed + return false; + } + return true; + } + + [HttpPost] + [FileUploadCleanupFilter(false)] + public async Task UploadLocalPackage() + { + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + + //TODO: App/Tree Permissions? + var model = new LocalPackageInstallModel + { + PackageGuid = Guid.NewGuid() + }; + + //get the files + foreach (var file in result.FileData) + { + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); + var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); + + //TODO: Only allow .zip + if (ext.InvariantEquals("zip") || ext.InvariantEquals("umb")) + { + //TODO: Currently it has to be here, it's not ideal but that's the way it is right now + var packageTempDir = IOHelper.MapPath(SystemDirectories.Data); + + //ensure it's there + Directory.CreateDirectory(packageTempDir); + + //copy it - must always be '.umb' for the installer thing to work + //the file name must be a GUID - this is what the packager expects (strange yes) + //because essentially this is creating a temporary package Id that will be used + //for unpacking/installing/etc... + model.ZipFilePath = model.PackageGuid + ".umb"; + var packageTempFileLocation = Path.Combine(packageTempDir, model.ZipFilePath); + File.Copy(file.LocalFileName, packageTempFileLocation, true); + + //Populate the model from the metadata in the package file (zip file) + PopulateFromPackageData(model); + + var validate = ValidateInstalledInternal(model.Name, model.Version); + + if (validate == false) + { + //this package is already installed + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("packager/packageAlreadyInstalled"))); + } + + } + else + { + model.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("media/disallowedFileType"), + SpeechBubbleIcon.Warning)); + } + + } + + return model; + + } + + /// + /// Gets the package from Our to install + /// + /// + /// [HttpGet] - public PackageInstallModel Fetch(string packageGuid) + public LocalPackageInstallModel Fetch(string packageGuid) { //Default path string path = Path.Combine("packages", packageGuid + ".umb"); @@ -38,26 +454,62 @@ namespace Umbraco.Web.Editors path = our.fetch(packageGuid, Security.CurrentUser.Id); } } - - var p = new PackageInstallModel(); - p.PackageGuid = Guid.Parse(packageGuid); - p.RepositoryGuid = Guid.Parse("65194810-1f85-11dd-bd0b-0800200c9a66"); - p.ZipFilePath = path; - //p.ZipFilePath = Path.Combine("temp", "package.umb"); - return p; + var model = new LocalPackageInstallModel + { + PackageGuid = Guid.Parse(packageGuid), + RepositoryGuid = Guid.Parse("65194810-1f85-11dd-bd0b-0800200c9a66"), + ZipFilePath = path + }; + + //Populate the model from the metadata in the package file (zip file) + PopulateFromPackageData(model); + + var validate = ValidateInstalledInternal(model.Name, model.Version); + + if (validate == false) + { + //this package is already installed + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("packager/packageAlreadyInstalled"))); + } + + return model; } + /// + /// Extracts the package zip and gets the packages information + /// + /// + /// [HttpPost] public PackageInstallModel Import(PackageInstallModel model) { var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); - model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, ins.Import(model.ZipFilePath)); + + var tempPath = ins.Import(model.ZipFilePath); + //now we need to check for version comparison + if (ins.RequirementsType == RequirementsType.Strict) + { + var packageMinVersion = new System.Version(ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch); + if (UmbracoVersion.Current < packageMinVersion) + { + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()}))); + } + } + + model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempPath); model.Id = ins.CreateManifest( IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString()); return model; } + /// + /// Installs the package files + /// + /// + /// [HttpPost] public PackageInstallModel InstallFiles(PackageInstallModel model) { @@ -67,7 +519,11 @@ namespace Umbraco.Web.Editors return model; } - + /// + /// Installs the packages data/business logic + /// + /// + /// [HttpPost] public PackageInstallModel InstallData(PackageInstallModel model) { @@ -77,11 +533,16 @@ namespace Umbraco.Web.Editors return model; } - + /// + /// Cleans up the package installation + /// + /// + /// [HttpPost] - public PackageInstallModel CleanUp(PackageInstallModel model) + public PackageInstallResult CleanUp(PackageInstallModel model) { var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); + var tempDir = IOHelper.MapPath(model.TemporaryDirectoryPath); ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath)); ins.InstallCleanUp(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath)); @@ -94,7 +555,24 @@ namespace Umbraco.Web.Editors global::Umbraco.Web._Legacy.Actions.Action.ReRegisterActionsAndHandlers(); - return model; + var redirectUrl = ""; + if (ins.Control.IsNullOrWhiteSpace() == false) + { + redirectUrl = string.Format("/developer/framed/{0}", + Uri.EscapeDataString( + string.Format("/umbraco/developer/Packages/installer.aspx?installing=custominstaller&dir={0}&pId={1}&customControl={2}&customUrl={3}", tempDir, model.Id, ins.Control, ins.Url))); + } + + return new PackageInstallResult + { + Id = model.Id, + ZipFilePath = model.ZipFilePath, + PackageGuid = model.PackageGuid, + RepositoryGuid = model.RepositoryGuid, + TemporaryDirectoryPath = model.TemporaryDirectoryPath, + PostInstallationPath = redirectUrl + }; + } diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs new file mode 100644 index 0000000000..001bd4e3e8 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Umbraco.Core.IO; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + + public abstract class AbstractConfigCheck : HealthCheck + { + private readonly ConfigurationService _configurationService; + private readonly ILocalizedTextService _textService; + + /// + /// Gets the config file path. + /// + public abstract string FilePath { get; } + + /// + /// Gets XPath statement to the config element to check. + /// + public abstract string XPath { get; } + + /// + /// Gets the values to compare against. + /// + public abstract IEnumerable Values { get; } + + /// + /// Gets the current value + /// + public string CurrentValue { get; set; } + + /// + /// Gets the provided value + /// + public string ProvidedValue { get; set; } + + /// + /// Gets the comparison type for checking the value. + /// + public abstract ValueComparisonType ValueComparisonType { get; } + + protected AbstractConfigCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + _configurationService = new ConfigurationService(AbsoluteFilePath, XPath); + } + + /// + /// Gets the name of the file. + /// + private string FileName + { + get { return Path.GetFileName(FilePath); } + } + + /// + /// Gets the absolute file path. + /// + private string AbsoluteFilePath + { + get { return IOHelper.MapPath(FilePath); } + } + + /// + /// Gets the message for when the check has succeeded. + /// + public virtual string CheckSuccessMessage + { + get + { + return _textService.Localize("healthcheck/checkSuccessMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath }); + } + } + + /// + /// Gets the message for when the check has failed. + /// + public virtual string CheckErrorMessage + { + get + { + return ValueComparisonType == ValueComparisonType.ShouldEqual + ? _textService.Localize("healthcheck/checkErrorMessageDifferentExpectedValue", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath }) + : _textService.Localize("healthcheck/checkErrorMessageUnexpectedValue", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath }); + } + } + + /// + /// Gets the rectify success message. + /// + public virtual string RectifySuccessMessage + { + get + { + var recommendedValue = Values.FirstOrDefault(v => v.IsRecommended); + var rectifiedValue = recommendedValue != null + ? recommendedValue.Value + : ProvidedValue; + return _textService.Localize("healthcheck/rectifySuccessMessage", + new[] + { + CurrentValue, + rectifiedValue, + XPath, + AbsoluteFilePath + }); + } + } + + /// + /// Gets a value indicating whether this check can be rectified automatically. + /// + public virtual bool CanRectify + { + get { return ValueComparisonType == ValueComparisonType.ShouldEqual; } + } + + /// + /// Gets a value indicating whether this check can be rectified automatically if a value is provided. + /// + public virtual bool CanRectifyWithValue + { + get { return ValueComparisonType == ValueComparisonType.ShouldNotEqual; } + } + + public override IEnumerable GetStatus() + { + var configValue = _configurationService.GetConfigurationValue(); + if (configValue.Success == false) + { + var message = configValue.Result; + return new[] { new HealthCheckStatus(message) { ResultType = StatusResultType.Error } }; + } + + CurrentValue = configValue.Result; + + var valueFound = Values.Any(value => string.Equals(CurrentValue, value.Value, StringComparison.InvariantCultureIgnoreCase)); + if (ValueComparisonType == ValueComparisonType.ShouldEqual && valueFound || ValueComparisonType == ValueComparisonType.ShouldNotEqual && valueFound == false) + { + var message = string.Format(CheckSuccessMessage, FileName, XPath, Values, CurrentValue); + return new[] { new HealthCheckStatus(message) { ResultType = StatusResultType.Success } }; + } + + // Declare the action for rectifying the config value + var rectifyAction = new HealthCheckAction("rectify", Id) + { + Name = _textService.Localize("healthcheck/rectifyButton"), + ValueRequired = CanRectifyWithValue, + }; + + var resultMessage = string.Format(CheckErrorMessage, FileName, XPath, Values, CurrentValue); + return new[] + { + new HealthCheckStatus(resultMessage) + { + ResultType = StatusResultType.Error, + Actions = CanRectify || CanRectifyWithValue ? new[] { rectifyAction } : new HealthCheckAction[0] + } + }; + } + + /// + /// Rectifies this check. + /// + /// + public virtual HealthCheckStatus Rectify() + { + if (ValueComparisonType == ValueComparisonType.ShouldNotEqual) + throw new InvalidOperationException(_textService.Localize("healthcheck/cannotRectifyShouldNotEqual")); + + var recommendedValue = Values.First(v => v.IsRecommended).Value; + return UpdateConfigurationValue(recommendedValue); + } + + /// + /// Rectifies this check with a provided value. + /// + /// Value provided + /// + public virtual HealthCheckStatus Rectify(string value) + { + if (ValueComparisonType == ValueComparisonType.ShouldEqual) + throw new InvalidOperationException(_textService.Localize("healthcheck/cannotRectifyShouldEqualWithValue")); + + if (string.IsNullOrWhiteSpace(value)) + throw new InvalidOperationException(_textService.Localize("healthcheck/valueToRectifyNotProvided")); + + // Need to track provided value in order to correctly put together the rectify message + ProvidedValue = value; + + return UpdateConfigurationValue(value); + } + + private HealthCheckStatus UpdateConfigurationValue(string value) + { + var updateConfigFile = _configurationService.UpdateConfigFile(value); + + if (updateConfigFile.Success == false) + { + var message = updateConfigFile.Result; + return new HealthCheckStatus(message) { ResultType = StatusResultType.Error }; + } + + var resultMessage = string.Format(RectifySuccessMessage, FileName, XPath, Values); + return new HealthCheckStatus(resultMessage) { ResultType = StatusResultType.Success }; + } + + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + return string.IsNullOrEmpty(action.ProvidedValue) + ? Rectify() + : Rectify(action.ProvidedValue); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/AcceptableConfiguration.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/AcceptableConfiguration.cs new file mode 100644 index 0000000000..e7c3036991 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/AcceptableConfiguration.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + public class AcceptableConfiguration + { + public string Value { get; set; } + public bool IsRecommended { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs new file mode 100644 index 0000000000..989046b464 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + [HealthCheck("61214FF3-FC57-4B31-B5CF-1D095C977D6D", "Debug Compilation Mode", + Description = "Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.", + Group = "Live Environment")] + public class CompilationDebugCheck : AbstractConfigCheck + { + private readonly ILocalizedTextService _textService; + + public CompilationDebugCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + public override string FilePath + { + get { return "~/Web.config"; } + } + + public override string XPath + { + get { return "/configuration/system.web/compilation/@debug"; } + } + + public override ValueComparisonType ValueComparisonType + { + get { return ValueComparisonType.ShouldEqual; } + } + + public override IEnumerable Values + { + get + { + return new List + { + new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() } + }; + } + } + + public override string CheckSuccessMessage + { + get { return _textService.Localize("healthcheck/compilationDebugCheckSuccessMessage"); } + } + + public override string CheckErrorMessage + { + get { return _textService.Localize("healthcheck/compilationDebugCheckErrorMessage"); } + } + + public override string RectifySuccessMessage + { + get { return _textService.Localize("healthcheck/compilationDebugCheckRectifySuccessMessage"); } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs new file mode 100644 index 0000000000..dd92cfa5ec --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.Xml; +using Umbraco.Core.Logging; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + //TODO: Add config transform for when config with specified XPath is not found + + public class ConfigurationService + { + private readonly string _configFilePath; + private readonly string _xPath; + private readonly ILocalizedTextService _textService; + + /// The absolute file location of the configuration file + /// The XPath to select the value + /// + public ConfigurationService(string configFilePath, string xPath) + { + _configFilePath = configFilePath; + _xPath = xPath; + _textService = UmbracoContext.Current.Application.Services.TextService; + } + + /// + /// Gets a value from a given configuration file with the given XPath + /// + public ConfigurationServiceResult GetConfigurationValue() + { + try + { + if (File.Exists(_configFilePath) == false) + return new ConfigurationServiceResult + { + Success = false, + Result = _textService.Localize("healthcheck/configurationServiceFileNotFound", new[] { _configFilePath }) + }; + + var xmlDocument = new XmlDocument(); + xmlDocument.Load(_configFilePath); + + var xmlNode = xmlDocument.SelectSingleNode(_xPath); + if (xmlNode == null) + return new ConfigurationServiceResult + { + Success = false, + Result = _textService.Localize("healthcheck/configurationServiceNodeNotFound", new[] { _xPath, _configFilePath }) + }; + + return new ConfigurationServiceResult + { + Success = true, + Result = string.Format(xmlNode.Value ?? xmlNode.InnerText) + }; + } + catch (Exception exception) + { + LogHelper.Error("Error trying to get configuration value", exception); + return new ConfigurationServiceResult + { + Success = false, + Result = _textService.Localize("healthcheck/configurationServiceError", new[] { exception.Message }) + }; + } + } + + /// + /// Updates a value in a given configuration file with the given XPath + /// + /// + /// + public ConfigurationServiceResult UpdateConfigFile(string value) + { + try + { + if (File.Exists(_configFilePath) == false) + return new ConfigurationServiceResult + { + Success = false, + Result = _textService.Localize("healthcheck/configurationServiceFileNotFound", new[] { _configFilePath }) + }; + + var xmlDocument = new XmlDocument { PreserveWhitespace = true }; + xmlDocument.Load(_configFilePath); + + var node = xmlDocument.SelectSingleNode(_xPath); + if (node == null) + return new ConfigurationServiceResult + { + Success = false, + Result = _textService.Localize("healthcheck/configurationServiceNodeNotFound", new[] { _xPath, _configFilePath }) + }; + + if (node.NodeType == XmlNodeType.Element) + node.InnerText = value; + else + node.Value = value; + + xmlDocument.Save(_configFilePath); + return new ConfigurationServiceResult { Success = true }; + } + catch (Exception exception) + { + LogHelper.Error("Error trying to update configuration", exception); + return new ConfigurationServiceResult + { + Success = false, + Result = _textService.Localize("healthcheck/configurationServiceError", new[] { exception.Message }) + }; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationServiceResult.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationServiceResult.cs new file mode 100644 index 0000000000..e88492d6b2 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationServiceResult.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + public class ConfigurationServiceResult + { + public bool Success { get; set; } + public string Result { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs new file mode 100644 index 0000000000..f6e47103e7 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Configuration; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + [HealthCheck("4090C0A1-2C52-4124-92DD-F028FD066A64", "Custom Errors", + Description = "Leaving custom errors off will display a complete stack trace to your visitors if an exception occurs.", + Group = "Live Environment")] + public class CustomErrorsCheck : AbstractConfigCheck + { + private readonly ILocalizedTextService _textService; + + public CustomErrorsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + public override string FilePath + { + get { return "~/Web.config"; } + } + + public override string XPath + { + get { return "/configuration/system.web/customErrors/@mode"; } + } + + public override ValueComparisonType ValueComparisonType + { + get { return ValueComparisonType.ShouldEqual; } + } + + public override IEnumerable Values + { + get + { + return new List + { + new AcceptableConfiguration { IsRecommended = true, Value = CustomErrorsMode.RemoteOnly.ToString() }, + new AcceptableConfiguration { IsRecommended = false, Value = "On" } + }; + } + } + + public override string CheckSuccessMessage + { + get + { + return _textService.Localize("healthcheck/customErrorsCheckSuccessMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); + } + } + + public override string CheckErrorMessage + { + get + { + return _textService.Localize("healthcheck/customErrorsCheckErrorMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); + } + } + + public override string RectifySuccessMessage + { + get + { + return _textService.Localize("healthcheck/customErrorsCheckRectifySuccessMessage", + new[] { Values.First(v => v.IsRecommended).Value }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs new file mode 100644 index 0000000000..0fe37e11e9 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + [HealthCheck("D0F7599E-9B2A-4D9E-9883-81C7EDC5616F", "Macro errors", + Description = "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.", + Group = "Configuration")] + public class MacroErrorsCheck : AbstractConfigCheck + { + private readonly ILocalizedTextService _textService; + + public MacroErrorsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + public override string FilePath + { + get { return "~/Config/umbracoSettings.config"; } + } + + public override string XPath + { + get { return "/settings/content/MacroErrors"; } + } + + public override ValueComparisonType ValueComparisonType + { + get { return ValueComparisonType.ShouldEqual; } + } + + public override IEnumerable Values + { + get + { + var values = new List + { + new AcceptableConfiguration + { + IsRecommended = true, + Value = "inline" + }, + new AcceptableConfiguration + { + IsRecommended = false, + Value = "silent" + } + }; + + return values; + } + } + + public override string CheckSuccessMessage + { + get + { + return _textService.Localize("healthcheck/macroErrorModeCheckSuccessMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); + } + } + + public override string CheckErrorMessage + { + get + { + return _textService.Localize("healthcheck/macroErrorModeCheckErrorMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); + } + } + + public override string RectifySuccessMessage + { + get + { + return _textService.Localize("healthcheck/macroErrorModeCheckRectifySuccessMessage", + new[] { Values.First(v => v.IsRecommended).Value }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs new file mode 100644 index 0000000000..bcdb0ebc24 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + [HealthCheck("3E2F7B14-4B41-452B-9A30-E67FBC8E1206", "Notification Email Settings", + Description = "If notifications are used, the 'from' email address should be specified and changed from the default value.", + Group = "Configuration")] + public class NotificationEmailCheck : AbstractConfigCheck + { + private readonly ILocalizedTextService _textService; + private const string DefaultFromEmail = "your@email.here"; + + public NotificationEmailCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + public override string FilePath + { + get { return "~/Config/umbracoSettings.config"; } + } + + public override string XPath + { + get { return "/settings/content/notifications/email"; } + } + + public override ValueComparisonType ValueComparisonType + { + get { return ValueComparisonType.ShouldNotEqual; } + } + + public override IEnumerable Values + { + get + { + return new List + { + new AcceptableConfiguration { IsRecommended = false, Value = DefaultFromEmail } + }; + } + } + + public override string CheckSuccessMessage + { + get { return _textService.Localize("healthcheck/notificationEmailsCheckSuccessMessage", new [] { CurrentValue } ); } + } + + public override string CheckErrorMessage + { + get { return _textService.Localize("healthcheck/notificationEmailsCheckErrorMessage", new[] { DefaultFromEmail }); } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs new file mode 100644 index 0000000000..d0e38815da --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + [HealthCheck("9BED6EF4-A7F3-457A-8935-B64E9AA8BAB3", "Trace Mode", + Description = "Leaving trace mode enabled can make valuable information about your system available to hackers.", + Group = "Live Environment")] + public class TraceCheck : AbstractConfigCheck + { + private readonly ILocalizedTextService _textService; + + public TraceCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + public override string FilePath + { + get { return "~/Web.config"; } + } + + public override string XPath + { + get { return "/configuration/system.web/trace/@enabled"; } + } + + public override ValueComparisonType ValueComparisonType + { + get { return ValueComparisonType.ShouldEqual; } + } + + public override IEnumerable Values + { + get + { + return new List + { + new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() } + }; + } + } + + public override string CheckSuccessMessage + { + get { return _textService.Localize("healthcheck/traceModeCheckSuccessMessage"); } + } + + public override string CheckErrorMessage + { + get { return _textService.Localize("healthcheck/traceModeCheckErrorMessage"); } + } + + public override string RectifySuccessMessage + { + get { return _textService.Localize("healthcheck/traceModeCheckRectifySuccessMessage"); } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs new file mode 100644 index 0000000000..654d7dd209 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + [HealthCheck("046A066C-4FB2-4937-B931-069964E16C66", "Try Skip IIS Custom Errors", + Description = "Starting with IIS 7.5, this must be set to true for Umbraco 404 pages to show. Otherwise, IIS will takeover and render its built-in error page.", + Group = "Configuration")] + public class TrySkipIisCustomErrorsCheck : AbstractConfigCheck + { + private readonly Version _serverVersion = HttpRuntime.IISVersion; + private readonly ILocalizedTextService _textService; + + public TrySkipIisCustomErrorsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + public override string FilePath + { + get { return "~/Config/umbracoSettings.config"; } + } + + public override string XPath + { + get { return "/settings/web.routing/@trySkipIisCustomErrors"; } + } + + public override ValueComparisonType ValueComparisonType + { + get { return ValueComparisonType.ShouldEqual; } + } + + public override IEnumerable Values + { + get + { + var recommendedValue = _serverVersion >= new Version("7.5.0") + ? bool.TrueString.ToLower() + : bool.FalseString.ToLower(); + return new List { new AcceptableConfiguration { IsRecommended = true, Value = recommendedValue } }; + } + } + + public override string CheckSuccessMessage + { + get + { + return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckSuccessMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); + } + } + + public override string CheckErrorMessage + { + get + { + return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckErrorMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); + } + } + + public override string RectifySuccessMessage + { + get + { + return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckRectifySuccessMessage", + new[] { Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/ValueComparisonType.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/ValueComparisonType.cs new file mode 100644 index 0000000000..88c8a94a09 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/ValueComparisonType.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Web.HealthCheck.Checks.Config +{ + public enum ValueComparisonType + { + ShouldEqual, + ShouldNotEqual, + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs new file mode 100644 index 0000000000..5f009a7b7c --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity +{ + /// + /// This moves the functionality from the XmlIntegrity check dashboard into a health check + /// + [HealthCheck( + "D999EB2B-64C2-400F-B50C-334D41F8589A", + "XML Data Integrity", + Description = "Checks the integrity of the XML data in Umbraco", + Group = "Data Integrity")] + public class XmlDataIntegrityHealthCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + + private const string CheckContentXmlTableAction = "checkContentXmlTable"; + private const string CheckMediaXmlTableAction = "checkMediaXmlTable"; + private const string CheckMembersXmlTableAction = "checkMembersXmlTable"; + + public XmlDataIntegrityHealthCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _sqlSyntax = HealthCheckContext.ApplicationContext.DatabaseContext.SqlSyntax; + _services = HealthCheckContext.ApplicationContext.Services; + _database = HealthCheckContext.ApplicationContext.DatabaseContext.Database; + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + private readonly ISqlSyntaxProvider _sqlSyntax; + private readonly ServiceContext _services; + private readonly UmbracoDatabase _database; + + /// + /// Get the status for this health check + /// + /// + public override IEnumerable GetStatus() + { + //return the statuses + return new[] { CheckContent(), CheckMedia(), CheckMembers() }; + } + + /// + /// Executes the action and returns it's status + /// + /// + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) + { + case CheckContentXmlTableAction: + _services.ContentService.RebuildXmlStructures(); + return CheckContent(); + case CheckMediaXmlTableAction: + _services.MediaService.RebuildXmlStructures(); + return CheckMedia(); + case CheckMembersXmlTableAction: + _services.MemberService.RebuildXmlStructures(); + return CheckMembers(); + default: + throw new ArgumentOutOfRangeException(); + } + } + + private HealthCheckStatus CheckMembers() + { + var total = _services.MemberService.Count(); + var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); + var subQuery = _database.Sql() + .Select("Count(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == memberObjectType); + var totalXml = _database.ExecuteScalar(subQuery); + + var actions = new List(); + if (totalXml != total) + actions.Add(new HealthCheckAction(CheckMembersXmlTableAction, Id)); + + return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMembers", new[] { totalXml.ToString(), total.ToString() })) + { + ResultType = totalXml == total ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private HealthCheckStatus CheckMedia() + { + var total = _services.MediaService.Count(); + var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); + var subQuery = _database.Sql() + .Select("Count(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType); + var totalXml = _database.ExecuteScalar(subQuery); + + var actions = new List(); + if (totalXml != total) + actions.Add(new HealthCheckAction(CheckMediaXmlTableAction, Id)); + + return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMedia", new[] { totalXml.ToString(), total.ToString() })) + { + ResultType = totalXml == total ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private HealthCheckStatus CheckContent() + { + var total = _services.ContentService.CountPublished(); + var subQuery = _database.Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId); + var totalXml = _database.ExecuteScalar("SELECT COUNT(*) FROM (" + subQuery.SQL + ") as tmp"); + + var actions = new List(); + if (totalXml != total) + actions.Add(new HealthCheckAction(CheckContentXmlTableAction, Id)); + + return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckContent", new[] { totalXml.ToString(), total.ToString() })) + { + ResultType = totalXml == total ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs new file mode 100644 index 0000000000..36e774287b --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.IO; +using Umbraco.Core.Services; +using Umbraco.Web.Install; + +namespace Umbraco.Web.HealthCheck.Checks.Permissions +{ + internal enum PermissionCheckRequirement + { + Required, + Optional + } + + internal enum PermissionCheckFor + { + Folder, + File + } + + [HealthCheck( + "53DBA282-4A79-4B67-B958-B29EC40FCC23", + "Folder & File Permissions", + Description = "Checks that the web server folder and file permissions are set correctly for Umbraco to run.", + Group = "Permissions")] + public class FolderAndFilePermissionsCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + + public FolderAndFilePermissionsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + /// + /// Get the status for this health check + /// + /// + public override IEnumerable GetStatus() + { + //return the statuses + return new[] { CheckFolderPermissions(), CheckFilePermissions() }; + } + + /// + /// Executes the action and returns it's status + /// + /// + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); + } + + private HealthCheckStatus CheckFolderPermissions() + { + // Create lists of paths to check along with a flag indicating if modify rights are required + // in ALL circumstances or just some + var pathsToCheck = new Dictionary + { + { SystemDirectories.AppCode, PermissionCheckRequirement.Optional }, + { SystemDirectories.Data, PermissionCheckRequirement.Required }, + { SystemDirectories.Packages, PermissionCheckRequirement.Required}, + { SystemDirectories.Preview, PermissionCheckRequirement.Required }, + { SystemDirectories.AppPlugins, PermissionCheckRequirement.Required }, + { SystemDirectories.Bin, PermissionCheckRequirement.Optional }, + { SystemDirectories.Config, PermissionCheckRequirement.Optional }, + { SystemDirectories.Css, PermissionCheckRequirement.Optional }, + { SystemDirectories.Masterpages, PermissionCheckRequirement.Optional }, + { SystemDirectories.Media, PermissionCheckRequirement.Optional }, + { SystemDirectories.Scripts, PermissionCheckRequirement.Optional }, + { SystemDirectories.Umbraco, PermissionCheckRequirement.Optional }, + { SystemDirectories.UmbracoClient, PermissionCheckRequirement.Optional }, + { SystemDirectories.UserControls, PermissionCheckRequirement.Optional }, + { SystemDirectories.MvcViews, PermissionCheckRequirement.Optional }, + { SystemDirectories.Xslt, PermissionCheckRequirement.Optional }, + }; + + // Run checks for required and optional paths for modify permission + List requiredFailedPaths; + List optionalFailedPaths; + var requiredPathCheckResult = FilePermissionHelper.TestDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); + var optionalPathCheckResult = FilePermissionHelper.TestDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); + + return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.Folder); + } + + private HealthCheckStatus CheckFilePermissions() + { + // Create lists of paths to check along with a flag indicating if modify rights are required + // in ALL circumstances or just some + var pathsToCheck = new Dictionary + { + { "~/Web.config", PermissionCheckRequirement.Optional }, + }; + + // Run checks for required and optional paths for modify permission + List requiredFailedPaths; + List optionalFailedPaths; + var requiredPathCheckResult = FilePermissionHelper.TestFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); + var optionalPathCheckResult = FilePermissionHelper.TestFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); + + return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.File); + } + + private static string[] GetPathsToCheck(Dictionary pathsToCheck, + PermissionCheckRequirement requirement) + { + return pathsToCheck + .Where(x => x.Value == requirement) + .Select(x => IOHelper.MapPath(x.Key)) + .OrderBy(x => x) + .ToArray(); + } + + private HealthCheckStatus GetStatus(bool requiredPathCheckResult, List requiredFailedPaths, + bool optionalPathCheckResult, IEnumerable optionalFailedPaths, + PermissionCheckFor checkingFor) + { + // Return error if any required parths fail the check, or warning if any optional ones do + var resultType = StatusResultType.Success; + var messageKey = string.Format("healthcheck/{0}PermissionsCheckMessage", + checkingFor == PermissionCheckFor.Folder ? "folder" : "file"); + var message = _textService.Localize(messageKey); + if (requiredPathCheckResult == false) + { + resultType = StatusResultType.Error; + messageKey = string.Format("healthcheck/required{0}PermissionFailed", + checkingFor == PermissionCheckFor.Folder ? "Folder" : "File"); + message = GetMessageForPathCheckFailure(messageKey, requiredFailedPaths); + } + else if (optionalPathCheckResult == false) + { + resultType = StatusResultType.Warning; + messageKey = string.Format("healthcheck/optional{0}PermissionFailed", + checkingFor == PermissionCheckFor.Folder ? "Folder" : "File"); + message = GetMessageForPathCheckFailure(messageKey, optionalFailedPaths); + } + + var actions = new List(); + return + new HealthCheckStatus(message) + { + ResultType = resultType, + Actions = actions + }; + } + + private string GetMessageForPathCheckFailure(string messageKey, IEnumerable failedPaths) + { + var rootFolder = IOHelper.MapPath("/"); + var failedFolders = failedPaths + .Select(x => ParseFolderFromFullPath(rootFolder, x)); + return _textService.Localize(messageKey, + new[] { string.Join(", ", failedFolders) }); + } + + private string ParseFolderFromFullPath(string rootFolder, string filePath) + { + return filePath.Replace(rootFolder, string.Empty); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs new file mode 100644 index 0000000000..2a57b4be0a --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.IO; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Security +{ + [HealthCheck( + "ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0", + "Click-Jacking Protection", + Description = "Checks if your site is allowed to be IFRAMed by another site and thus would be susceptible to click-jacking.", + Group = "Security")] + public class ClickJackingCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + + private const string SetFrameOptionsHeaderInConfigActiobn = "setFrameOptionsHeaderInConfig"; + + private const string XFrameOptionsHeader = "X-Frame-Options"; + private const string XFrameOptionsValue = "sameorigin"; // Note can't use "deny" as that would prevent Umbraco itself using IFRAMEs + + public ClickJackingCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + /// + /// Get the status for this health check + /// + /// + public override IEnumerable GetStatus() + { + //return the statuses + return new[] { CheckForFrameOptionsHeader() }; + } + + /// + /// Executes the action and returns it's status + /// + /// + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) + { + case SetFrameOptionsHeaderInConfigActiobn: + return SetFrameOptionsHeaderInConfig(); + default: + throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist"); + } + } + + private HealthCheckStatus CheckForFrameOptionsHeader() + { + var message = string.Empty; + var success = false; + var url = HealthCheckContext.HttpContext.Request.Url; + + // Access the site home page and check for the click-jack protection header or meta tag + var address = string.Format("http://{0}:{1}", url.Host.ToLower(), url.Port); + var request = WebRequest.Create(address); + request.Method = "GET"; + try + { + var response = request.GetResponse(); + + // Check first for header + success = DoHeadersContainFrameOptions(response); + + // If not found, check for meta-tag + if (success == false) + { + success = DoMetaTagsContainFrameOptions(response); + } + + message = success + ? _textService.Localize("healthcheck/clickJackingCheckHeaderFound") + : _textService.Localize("healthcheck/clickJackingCheckHeaderNotFound"); + } + catch (Exception ex) + { + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { address, ex.Message }); + } + + var actions = new List(); + if (success == false) + { + actions.Add(new HealthCheckAction(SetFrameOptionsHeaderInConfigActiobn, Id) + { + Name = _textService.Localize("healthcheck/clickJackingSetHeaderInConfig"), + Description = _textService.Localize("healthcheck/clickJackingSetHeaderInConfigDescription") + }); + } + + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private static bool DoHeadersContainFrameOptions(WebResponse response) + { + return response.Headers.AllKeys.Contains(XFrameOptionsHeader); + } + + private static bool DoMetaTagsContainFrameOptions(WebResponse response) + { + using (var stream = response.GetResponseStream()) + { + if (stream == null) return false; + using (var reader = new StreamReader(stream)) + { + var html = reader.ReadToEnd(); + var metaTags = ParseMetaTags(html); + return metaTags.ContainsKey(XFrameOptionsHeader); + } + } + } + + private static Dictionary ParseMetaTags(string html) + { + var regex = new Regex("() + .ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value); + } + + private HealthCheckStatus SetFrameOptionsHeaderInConfig() + { + var errorMessage = string.Empty; + var success = SaveHeaderToConfigFile(out errorMessage); + + if (success) + { + return + new HealthCheckStatus(_textService.Localize("healthcheck/clickJackingSetHeaderInConfigSuccess")) + { + ResultType = StatusResultType.Success + }; + } + + return + new HealthCheckStatus(_textService.Localize("healthcheck/clickJackingSetHeaderInConfigError", new [] { errorMessage })) + { + ResultType = StatusResultType.Error + }; + } + + private static bool SaveHeaderToConfigFile(out string errorMessage) + { + try + { + // There don't look to be any useful classes defined in https://msdn.microsoft.com/en-us/library/system.web.configuration(v=vs.110).aspx + // for working with the customHeaders section, so working with the XML directly. + var configFile = IOHelper.MapPath("~/Web.config"); + var doc = XDocument.Load(configFile); + var systemWebServerElement = doc.XPathSelectElement("/configuration/system.webServer"); + var httpProtocolElement = systemWebServerElement.Element("httpProtocol"); + if (httpProtocolElement == null) + { + httpProtocolElement = new XElement("httpProtocol"); + systemWebServerElement.Add(httpProtocolElement); + } + + var customHeadersElement = httpProtocolElement.Element("customHeaders"); + if (customHeadersElement == null) + { + customHeadersElement = new XElement("customHeaders"); + httpProtocolElement.Add(customHeadersElement); + } + + var removeHeaderElement = customHeadersElement.Elements("remove") + .SingleOrDefault(x => x.Attribute("name") != null && + x.Attribute("name").Value == XFrameOptionsHeader); + if (removeHeaderElement == null) + { + removeHeaderElement = new XElement("remove"); + removeHeaderElement.Add(new XAttribute("name", XFrameOptionsHeader)); + customHeadersElement.Add(removeHeaderElement); + } + + var addHeaderElement = customHeadersElement.Elements("add") + .SingleOrDefault(x => x.Attribute("name") != null && + x.Attribute("name").Value == XFrameOptionsHeader); + if (addHeaderElement == null) + { + addHeaderElement = new XElement("add"); + addHeaderElement.Add(new XAttribute("name", XFrameOptionsHeader)); + addHeaderElement.Add(new XAttribute("value", XFrameOptionsValue)); + customHeadersElement.Add(addHeaderElement); + } + + doc.Save(configFile); + + errorMessage = string.Empty; + return true; + } + catch (Exception ex) + { + errorMessage = ex.Message; + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs new file mode 100644 index 0000000000..af1b15818a --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Web; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Security +{ + [HealthCheck( + "92ABBAA2-0586-4089-8AE2-9A843439D577", + "Excessive Headers", + Description = "Checks to see if your site is revealing information in it's headers that gives away unnecessary details about the technology used to build and host it.", + Group = "Security")] + public class ExcessiveHeadersCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + + public ExcessiveHeadersCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + /// + /// Get the status for this health check + /// + /// + public override IEnumerable GetStatus() + { + //return the statuses + return new[] { CheckForHeaders() }; + } + + /// + /// Executes the action and returns it's status + /// + /// + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + throw new InvalidOperationException("ExcessiveHeadersCheck has no executable actions"); + } + + private HealthCheckStatus CheckForHeaders() + { + var message = string.Empty; + var success = false; + var url = HealthCheckContext.HttpContext.Request.Url; + + // Access the site home page and check for the headers + var address = string.Format("http://{0}:{1}", url.Host.ToLower(), url.Port); + var request = WebRequest.Create(address); + request.Method = "HEAD"; + try + { + var response = request.GetResponse(); + var allHeaders = response.Headers.AllKeys; + var headersToCheckFor = new [] {"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version"}; + var headersFound = allHeaders + .Intersect(headersToCheckFor) + .ToArray(); + success = headersFound.Any() == false; + message = success + ? _textService.Localize("healthcheck/excessiveHeadersNotFound") + : _textService.Localize("healthcheck/excessiveHeadersFound", new [] { string.Join(", ", headersFound) }); + } + catch (Exception ex) + { + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { address, ex.Message }); + } + + var actions = new List(); + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Warning, + Actions = actions + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs new file mode 100644 index 0000000000..80853c01d8 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Web; +using Umbraco.Core.IO; +using Umbraco.Core.Services; +using Umbraco.Web.HealthCheck.Checks.Config; + +namespace Umbraco.Web.HealthCheck.Checks.Security +{ + [HealthCheck( + "EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7", + "HTTPS Configuration", + Description = "Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.", + Group = "Security")] + public class HttpsCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + + private const string FixHttpsSettingAction = "fixHttpsSetting"; + + public HttpsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + /// + /// Get the status for this health check + /// + /// + public override IEnumerable GetStatus() + { + //return the statuses + return new[] { CheckIfCurrentSchemeIsHttps(), CheckHttpsConfigurationSetting(), CheckForValidCertificate() }; + } + + /// + /// Executes the action and returns it's status + /// + /// + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) + { + case FixHttpsSettingAction: + return FixHttpsSetting(); + default: + throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist"); + } + } + + private HealthCheckStatus CheckForValidCertificate() + { + var message = string.Empty; + var success = false; + var url = HealthCheckContext.HttpContext.Request.Url; + + // Attempt to access the site over HTTPS to see if it HTTPS is supported + // and a valid certificate has been configured + var address = string.Format("https://{0}:{1}", url.Host.ToLower(), url.Port); + var request = (HttpWebRequest)WebRequest.Create(address); + request.Method = "HEAD"; + + try + { + var response = (HttpWebResponse)request.GetResponse(); + success = response.StatusCode == HttpStatusCode.OK; + } + catch (Exception ex) + { + var exception = ex as WebException; + if (exception != null) + { + message = exception.Status == WebExceptionStatus.TrustFailure + ? _textService.Localize("healthcheck/httpsCheckInvalidCertificate", new [] { exception.Message }) + : _textService.Localize("healthcheck/httpsCheckInvalidUrl", new [] { address, exception.Message }); + } + else + { + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { address, ex.Message }); + } + } + + var actions = new List(); + + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private HealthCheckStatus CheckIfCurrentSchemeIsHttps() + { + var uri = HttpContext.Current.Request.Url; + var success = uri.Scheme == "https"; + + var actions = new List(); + + return + new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" })) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private HealthCheckStatus CheckHttpsConfigurationSetting() + { + var httpsSettingEnabled = Core.Configuration.GlobalSettings.UseSSL; + var uri = HttpContext.Current.Request.Url; + var actions = new List(); + + string resultMessage; + StatusResultType resultType; + if (uri.Scheme != "https") + { + resultMessage = _textService.Localize("healthcheck/httpsCheckConfigurationRectifyNotPossible"); + resultType = StatusResultType.Info; + } + else + { + if (httpsSettingEnabled == false) + actions.Add(new HealthCheckAction(FixHttpsSettingAction, Id) + { + Name = _textService.Localize("healthcheck/httpsCheckEnableHttpsButton"), + Description = _textService.Localize("healthcheck/httpsCheckEnableHttpsDescription") + }); + + resultMessage = _textService.Localize("healthcheck/httpsCheckConfigurationCheckResult", + new[] {httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not"}); + resultType = httpsSettingEnabled ? StatusResultType.Success: StatusResultType.Error; + } + + return + new HealthCheckStatus(resultMessage) + { + ResultType = resultType, + Actions = actions + }; + } + + private HealthCheckStatus FixHttpsSetting() + { + var configFile = IOHelper.MapPath("~/Web.config"); + const string xPath = "/configuration/appSettings/add[@key='umbracoUseSSL']/@value"; + var configurationService = new ConfigurationService(configFile, xPath); + var updateConfigFile = configurationService.UpdateConfigFile("true"); + + if (updateConfigFile.Success) + { + return + new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsSuccess")) + { + ResultType = StatusResultType.Success + }; + } + + return + new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsError", new [] { updateConfigFile.Result })) + { + ResultType = StatusResultType.Error + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs new file mode 100644 index 0000000000..a1f085865c --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Configuration; +using System.Net.Sockets; +using System.Web.Configuration; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Services +{ + [HealthCheck( + "1B5D221B-CE99-4193-97CB-5F3261EC73DF", + "SMTP Settings", + Description = "Checks that valid settings for sending emails are in place.", + Group = "Services")] + public class SmtpCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + + public SmtpCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + { + _textService = healthCheckContext.ApplicationContext.Services.TextService; + } + + /// + /// Get the status for this health check + /// + /// + public override IEnumerable GetStatus() + { + //return the statuses + return new[] { CheckSmtpSettings() }; + } + + /// + /// Executes the action and returns it's status + /// + /// + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + throw new InvalidOperationException("SmtpCheck has no executable actions"); + } + + private HealthCheckStatus CheckSmtpSettings() + { + const int DefaultSmtpPort = 25; + var message = string.Empty; + var success = false; + + var config = WebConfigurationManager.OpenWebConfiguration(HealthCheckContext.HttpContext.Request.ApplicationPath); + var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); + if (settings == null) + { + message = _textService.Localize("healthcheck/smtpMailSettingsNotFound"); + } + else + { + var host = settings.Smtp.Network.Host; + var port = settings.Smtp.Network.Port == 0 ? DefaultSmtpPort : settings.Smtp.Network.Port; + if (string.IsNullOrEmpty(host)) + { + message = _textService.Localize("healthcheck/smtpMailSettingsHostNotConfigured"); + } + else + { + success = CanMakeSmtpConnection(host, port); + message = success + ? _textService.Localize("healthcheck/smtpMailSettingsConnectionSuccess") + : _textService.Localize("healthcheck/smtpMailSettingsConnectionFail", new [] { host, port.ToString() }); + } + } + + var actions = new List(); + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private bool CanMakeSmtpConnection(string host, int port) + { + try + { + using (var client = new TcpClient()) + { + client.Connect(host, port); + using (var stream = client.GetStream()) + { + using (var writer = new StreamWriter(stream)) + using (var reader = new StreamReader(stream)) + { + writer.WriteLine("EHLO " + host); + writer.Flush(); + reader.ReadLine(); + return true; + } + } + } + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheck.cs b/src/Umbraco.Web/HealthCheck/HealthCheck.cs new file mode 100644 index 0000000000..ec1358947a --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheck.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core; + +namespace Umbraco.Web.HealthCheck +{ + /// + /// The abstract health check class + /// + [DataContract(Name = "healtCheck", Namespace = "")] + public abstract class HealthCheck + { + protected HealthCheck(HealthCheckContext healthCheckContext) + { + HealthCheckContext = healthCheckContext; + //Fill in the metadata + var thisType = this.GetType(); + var meta = thisType.GetCustomAttribute(false); + if (meta == null) + throw new InvalidOperationException( + string.Format("The health check {0} requires a {1}", thisType, typeof(HealthCheckAttribute))); + Name = meta.Name; + Description = meta.Description; + Group = meta.Group; + Id = meta.Id; + } + + [IgnoreDataMember] + public HealthCheckContext HealthCheckContext { get; private set; } + + [DataMember(Name = "id")] + public Guid Id { get; private set; } + + [DataMember(Name = "name")] + public string Name { get; private set; } + + [DataMember(Name = "description")] + public string Description { get; private set; } + + [DataMember(Name = "group")] + public string Group { get; private set; } + + /// + /// Get the status for this health check + /// + /// + public abstract IEnumerable GetStatus(); + + /// + /// Executes the action and returns it's status + /// + /// + /// + public abstract HealthCheckStatus ExecuteAction(HealthCheckAction action); + + //TODO: What else? + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs b/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs new file mode 100644 index 0000000000..526ca0ece3 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck +{ + [DataContract(Name = "healtCheckAction", Namespace = "")] + public class HealthCheckAction + { + /// + /// Empty ctor used for serialization + /// + public HealthCheckAction() { } + + /// + /// Default ctor + /// + /// + /// + public HealthCheckAction(string alias, Guid healthCheckId) + { + Alias = alias; + HealthCheckId = healthCheckId; + } + + /// + /// The alias of the action - this is used by the Health Check instance to execute the action + /// + [DataMember(Name = "alias")] + public string Alias { get; set; } + + /// + /// The Id of the Health Check instance + /// + /// + /// This is used to find the Health Check instance to execute this action + /// + [DataMember(Name = "healthCheckId")] + public Guid HealthCheckId { get; set; } + + /// + /// This could be used if the status has a custom view that specifies some parameters to be sent to the server + /// when an action needs to be executed + /// + [DataMember(Name = "actionParameters")] + public Dictionary ActionParameters { get; set; } + + + /// + /// The name of the action - this is used to name the fix button + /// + [DataMember(Name = "name")] + private string _name = UmbracoContext.Current.Application.Services.TextService.Localize("healthcheck/rectifyButton"); + public string Name + { + get { return _name; } + set { _name = value; } + } + + /// + /// The description of the action - this is used to give a description before executing the action + /// + [DataMember(Name = "description")] + public string Description { get; set; } + + /// + /// Indicates if a value is required to rectify the issue + /// + [DataMember(Name = "valueRequired")] + public bool ValueRequired { get; set; } + + /// + /// Provides a value to rectify the issue + /// + [DataMember(Name = "providedValue")] + public string ProvidedValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckAttribute.cs b/src/Umbraco.Web/HealthCheck/HealthCheckAttribute.cs new file mode 100644 index 0000000000..885af8b3ba --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckAttribute.cs @@ -0,0 +1,26 @@ +using System; + +namespace Umbraco.Web.HealthCheck +{ + /// + /// Metadata attribute for Health checks + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class HealthCheckAttribute : Attribute + { + public HealthCheckAttribute(string id, string name) + { + Id = new Guid(id); + Name = name; + } + + public string Name { get; private set; } + public string Description { get; set; } + + public string Group { get; set; } + + public Guid Id { get; private set; } + + //TODO: Do we need more metadata? + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs b/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs new file mode 100644 index 0000000000..4ec02a2386 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs @@ -0,0 +1,27 @@ +using System; +using System.Web; +using Umbraco.Core; + +namespace Umbraco.Web.HealthCheck +{ + /// + /// Context exposing all services that could be required for health check classes to perform and/or fix their checks + /// + public class HealthCheckContext + { + public HealthCheckContext(HttpContextBase httpContext, UmbracoContext umbracoContext) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + HttpContext = httpContext; + UmbracoContext = umbracoContext; + ApplicationContext = UmbracoContext.Application; + } + + public HttpContextBase HttpContext { get; private set; } + public UmbracoContext UmbracoContext { get; private set; } + public ApplicationContext ApplicationContext { get; private set; } + + //TODO: Do we need any more info/service exposed here? + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs new file mode 100644 index 0000000000..919df88962 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using Umbraco.Web.Editors; + +namespace Umbraco.Web.HealthCheck +{ + /// + /// The API controller used to display the health check info and execute any actions + /// + public class HealthCheckController : UmbracoAuthorizedJsonController + { + private readonly IHealthCheckResolver _healthCheckResolver; + + public HealthCheckController() + { + _healthCheckResolver = HealthCheckResolver.Current; + } + + public HealthCheckController(IHealthCheckResolver healthCheckResolver) + { + _healthCheckResolver = healthCheckResolver; + } + + /// + /// Gets a grouped list of health checks, but doesn't actively check the status of each health check. + /// + /// Returns a collection of anonymous objects representing each group. + public object GetAllHealthChecks() + { + var groups = _healthCheckResolver.HealthChecks + .GroupBy(x => x.Group) + .OrderBy(x => x.Key); + var healthCheckGroups = new List(); + foreach (var healthCheckGroup in groups) + { + var hcGroup = new HealthCheckGroup + { + Name = healthCheckGroup.Key, + Checks = healthCheckGroup + .OrderBy(x => x.Name) + .ToList() + }; + healthCheckGroups.Add(hcGroup); + } + + return healthCheckGroups; + } + + public object GetStatus(Guid id) + { + var check = _healthCheckResolver.HealthChecks.FirstOrDefault(x => x.Id == id); + if (check == null) throw new InvalidOperationException("No health check found with ID " + id); + + return check.GetStatus(); + } + + [HttpPost] + public HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + var check = _healthCheckResolver.HealthChecks.FirstOrDefault(x => x.Id == action.HealthCheckId); + if (check == null) throw new InvalidOperationException("No health check found with id " + action.HealthCheckId); + + return check.ExecuteAction(action); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckGroup.cs b/src/Umbraco.Web/HealthCheck/HealthCheckGroup.cs new file mode 100644 index 0000000000..f01c65f854 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckGroup.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.HealthCheck +{ + [DataContract(Name = "healthCheckGroup", Namespace = "")] + public class HealthCheckGroup + { + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "checks")] + public List Checks { get; set; } + } +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs new file mode 100644 index 0000000000..d3e2dc794d --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Web; +using Umbraco.Core.Logging; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Web.HealthCheck +{ + /// + /// Resolves all health check instances + /// + /// + /// Each instance scoped to the lifespan of the http request + /// + internal class HealthCheckResolver : LazyManyObjectsResolverBase, IHealthCheckResolver + { + public HealthCheckResolver(ILogger logger, Func> lazyTypeList) + : base(new HealthCheckServiceProvider(), logger, lazyTypeList, ObjectLifetimeScope.HttpRequest) + { + } + + /// + /// Returns all health check instances + /// + public IEnumerable HealthChecks => Values; + + /// + /// This will ctor the HealthCheck instances + /// + /// + /// This is like a super crappy DI - in v8 we have real DI + /// + private class HealthCheckServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) + { + var normalArgs = new[] { typeof(HealthCheckContext) }; + var found = serviceType.GetConstructor(normalArgs); + if (found != null) + { + return found.Invoke(new object[] + { + new HealthCheckContext(new HttpContextWrapper(HttpContext.Current), UmbracoContext.Current) + }); + } + + //use normal ctor + return Activator.CreateInstance(serviceType); + } + } + } +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckStatus.cs b/src/Umbraco.Web/HealthCheck/HealthCheckStatus.cs new file mode 100644 index 0000000000..c966cadfa7 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckStatus.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Web.HealthCheck +{ + /// + /// The status returned for a health check when it performs it check + /// TODO: This model will be used in the WebApi result so needs attributes for JSON usage + /// + [DataContract(Name = "healtCheckStatus", Namespace = "")] + public class HealthCheckStatus + { + public HealthCheckStatus(string message) + { + Message = message; + Actions = Enumerable.Empty(); + } + + /// + /// The status message + /// + [DataMember(Name = "message")] + public string Message { get; private set; } + + /// + /// The status description if one is necessary + /// + [DataMember(Name = "description")] + public string Description { get; set; } + + /// + /// This is optional but would allow a developer to specify a path to an angular html view + /// in order to either show more advanced information and/or to provide input for the admin + /// to configure how an action is executed + /// + [DataMember(Name = "view")] + public string View { get; set; } + + /// + /// The status type + /// + [DataMember(Name = "resultType")] + public StatusResultType ResultType { get; set; } + + /// + /// The potential actions to take (in any) + /// + [DataMember(Name = "actions")] + public IEnumerable Actions { get; set; } + + //TODO: What else? + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/IHealthCheckResolver.cs b/src/Umbraco.Web/HealthCheck/IHealthCheckResolver.cs new file mode 100644 index 0000000000..795b61198e --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/IHealthCheckResolver.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.HealthCheck +{ + public interface IHealthCheckResolver + { + IEnumerable HealthChecks { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/StatusResultType.cs b/src/Umbraco.Web/HealthCheck/StatusResultType.cs new file mode 100644 index 0000000000..8e5fbb4cc9 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/StatusResultType.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Web.HealthCheck +{ + public enum StatusResultType + { + Success, + Warning, + Error, + Info + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs index a8054cfbd4..416a9d02e2 100644 --- a/src/Umbraco.Web/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web.Install { errorReport = new List(); bool succes = true; - foreach (string file in PermissionFiles) + foreach (string file in files) { bool result = OpenFileForWrite(IOHelper.MapPath(file)); if (result == false) diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs index d2aa040f03..9ededd00d0 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs @@ -61,8 +61,11 @@ namespace Umbraco.Web.Install.InstallSteps } else { + var password = string.Format("'{0}'", database.Password); + password = password.Replace("&", "&").Replace(">", ">").Replace("<", "<").Replace("\"", """).Replace("'", "''"); + dbContext.ConfigureDatabaseConnection( - database.Server, database.DatabaseName, database.Login, database.Password, + database.Server, database.DatabaseName, database.Login, password, database.DatabaseType.ToString()); } } diff --git a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs index 2e303f7018..42bca03498 100644 --- a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs @@ -1,3 +1,6 @@ +using Semver; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -18,5 +21,43 @@ namespace Umbraco.Web.Install.InstallSteps { return null; } + + public override object ViewModel + { + get + { + var currentVersion = CurrentVersion().GetVersion(3).ToString(); + var newVersion = UmbracoVersion.Current.ToString(); + var reportUrl = string.Format("https://our.umbraco.org/contribute/releases/compare?from={0}&to={1}¬es=1", currentVersion, newVersion); + + return new + { + currentVersion = currentVersion, + newVersion = newVersion, + reportUrl = reportUrl + }; + + } + } + + /// + /// Gets the Current Version of the Umbraco Site before an upgrade + /// by using the last/most recent Umbraco Migration that has been run + /// + /// A SemVersion of the latest Umbraco DB Migration run + private SemVersion CurrentVersion() + { + //Set a default version of 0.0.0 + var version = new SemVersion(0); + + //If we have a db context available, if we don't then we are not installed anyways + if (ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured && ApplicationContext.Current.DatabaseContext.CanConnect) + { + version = ApplicationContext.Current.DatabaseContext.ValidateDatabaseSchema().DetermineInstalledVersionByMigrations(ApplicationContext.Current.Services.MigrationEntryService); + } + + return version; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs b/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs new file mode 100644 index 0000000000..3b1bf69faf --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract] + public class InstalledPackageModel + { + [DataMember(Name = "id")] + public int Id { get; set; } + + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "author")] + public string Author { get; set; } + + [DataMember(Name = "files")] + public IEnumerable Files { get; set; } + + [DataMember(Name = "version")] + public string Version { get; set; } + + [DataMember(Name = "url")] + public string Url { get; set; } + + [DataMember(Name = "license")] + public string License { get; set; } + + [DataMember(Name = "licenseUrl")] + public string LicenseUrl { get; set; } + + [DataMember(Name = "iconUrl")] + public string IconUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs new file mode 100644 index 0000000000..7793787670 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the + /// temporary files that were created. + /// + [DataContract] + internal class PostedFiles : IHaveUploadedFiles, INotificationModel + { + public PostedFiles() + { + UploadedFiles = new List(); + Notifications = new List(); + } + public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/LocalPackageInstallModel.cs b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs new file mode 100644 index 0000000000..a107ac2428 --- /dev/null +++ b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models +{ + /// + /// A model that represents uploading a local package + /// + [DataContract(Name = "localPackageInstallModel")] + public class LocalPackageInstallModel : PackageInstallModel, IHaveUploadedFiles, INotificationModel + { + public LocalPackageInstallModel() + { + UploadedFiles = new List(); + Notifications = new List(); + } + + public List UploadedFiles { get; } + + [DataMember(Name = "notifications")] + public List Notifications { get; } + + /// + /// A flag to determine if this package is compatible to be installed + /// + [DataMember(Name = "isCompatible")] + public bool IsCompatible { get; set; } + + /// + /// The minimum umbraco version that this package is pinned to + /// + [DataMember(Name = "umbracoVersion")] + public string UmbracoVersion { get; set; } + + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "url")] + public string Url { get; set; } + + [DataMember(Name = "version")] + public string Version { get; set; } + + [DataMember(Name = "containsUnsecureFiles")] + public bool ContainsUnsecureFiles { get; set; } + + [DataMember(Name = "containsTemplateConflicts")] + public bool ContainsTemplateConflicts { get; set; } + + [DataMember(Name = "containsStyleSheetConflicts")] + public bool ContainsStyleSheetConflicts { get; set; } + + [DataMember(Name = "containsMacroConflict")] + public bool ContainsMacroConflict { get; set; } + + [DataMember(Name = "containsBinaryFileErrors")] + public bool ContainsBinaryFileErrors { get; set; } + + [DataMember(Name = "conflictingTemplateAliases")] + public IDictionary ConflictingTemplateAliases { get; set; } + + [DataMember(Name = "conflictingStyleSheetNames")] + public IDictionary ConflictingStyleSheetNames { get; set; } + + [DataMember(Name = "conflictingMacroAliases")] + public IDictionary ConflictingMacroAliases { get; set; } + + [DataMember(Name = "readMe")] + public string ReadMe { get; set; } + + [DataMember(Name = "licenseUrl")] + public string LicenseUrl { get; set; } + + [DataMember(Name = "license")] + public string License { get; set; } + + [DataMember(Name = "authorUrl")] + public string AuthorUrl { get; set; } + + [DataMember(Name = "author")] + public string Author { get; set; } + + [DataMember(Name = "iconUrl")] + public string IconUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index b3e851a25e..d8dd701731 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -180,7 +180,11 @@ namespace Umbraco.Web.Models.Mapping Label = localizedText.Localize("content/releaseDate"), Value = display.ReleaseDate.HasValue ? display.ReleaseDate.Value.ToIsoString() : null, //Not editible for people without publish permission (U4-287) - View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View + View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View, + Config = new Dictionary + { + {"offsetTime", "1"} + } //TODO: Fix up hard coded datepicker } , new ContentPropertyDisplay @@ -189,7 +193,11 @@ namespace Umbraco.Web.Models.Mapping Label = localizedText.Localize("content/unpublishDate"), Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null, //Not editible for people without publish permission (U4-287) - View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View + View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View, + Config = new Dictionary + { + {"offsetTime", "1"} + } //TODO: Fix up hard coded datepicker }, new ContentPropertyDisplay @@ -222,7 +230,7 @@ namespace Umbraco.Web.Models.Mapping && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) { var currentDocumentType = contentTypeService.Get(display.ContentTypeAlias); - var currentDocumentTypeName = currentDocumentType == null ? string.Empty : currentDocumentType.Name; + var currentDocumentTypeName = currentDocumentType == null ? string.Empty : localizedText.UmbracoDictionaryTranslate(currentDocumentType.Name); var currentDocumentTypeId = currentDocumentType == null ? string.Empty : currentDocumentType.Id.ToString(CultureInfo.InvariantCulture); //TODO: Hard coding this is not good diff --git a/src/Umbraco.Web/Models/PackageInstallModel.cs b/src/Umbraco.Web/Models/PackageInstallModel.cs index 34b28843b6..f903fc1880 100644 --- a/src/Umbraco.Web/Models/PackageInstallModel.cs +++ b/src/Umbraco.Web/Models/PackageInstallModel.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; @@ -10,7 +9,7 @@ namespace Umbraco.Web.Models [DataContract(Name = "packageInstallModel")] public class PackageInstallModel { - [DataMember(Name="id")] + [DataMember(Name = "id")] public int Id { get; set; } [DataMember(Name = "packageGuid")] @@ -24,5 +23,7 @@ namespace Umbraco.Web.Models [DataMember(Name = "zipFilePath")] public string ZipFilePath { get; set; } + + } } diff --git a/src/Umbraco.Web/Models/PackageInstallResult.cs b/src/Umbraco.Web/Models/PackageInstallResult.cs new file mode 100644 index 0000000000..bb76bc663b --- /dev/null +++ b/src/Umbraco.Web/Models/PackageInstallResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + /// + /// Model that is returned when a package is totally finished installing + /// + public class PackageInstallResult : PackageInstallModel + { + [DataMember(Name = "postInstallationPath")] + public string PostInstallationPath { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoRequireHttpsAttribute.cs b/src/Umbraco.Web/Mvc/UmbracoRequireHttpsAttribute.cs new file mode 100644 index 0000000000..129e3a6596 --- /dev/null +++ b/src/Umbraco.Web/Mvc/UmbracoRequireHttpsAttribute.cs @@ -0,0 +1,39 @@ +using System.Web.Mvc; +using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; + +namespace Umbraco.Web.Mvc +{ + /// + /// If umbracoUseSSL property in web.config is set to true, this filter will redirect any http access to https. + /// + public class UmbracoRequireHttpsAttribute : RequireHttpsAttribute + { + /// + /// If umbracoUseSSL is true and we have a non-HTTPS request, handle redirect. + /// + /// Filter context + protected override void HandleNonHttpsRequest(AuthorizationContext filterContext) + { + // If umbracoUseSSL is set, let base method handle redirect. Otherwise, we don't care. + if (GlobalSettings.UseSSL) + { + base.HandleNonHttpsRequest(filterContext); + } + } + + /// + /// Check to see if HTTPS is currently being used if umbracoUseSSL is true. + /// + /// Filter context + public override void OnAuthorization(AuthorizationContext filterContext) + { + // If umbracoSSL is set, let base method handle checking for HTTPS. Otherwise, we don't care. + if (GlobalSettings.UseSSL) + { + base.OnAuthorization(filterContext); + } + } + + + } +} diff --git a/src/Umbraco.Web/NotificationServiceExtensions.cs b/src/Umbraco.Web/NotificationServiceExtensions.cs index 0a63aec04a..a4ad00ee11 100644 --- a/src/Umbraco.Web/NotificationServiceExtensions.cs +++ b/src/Umbraco.Web/NotificationServiceExtensions.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Core.Models; using Umbraco.Web._Legacy.Actions; +using System.Collections.Generic; namespace Umbraco.Web { @@ -26,6 +27,16 @@ namespace Umbraco.Web service.SendNotification(entity, action, UmbracoContext.Current); } + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, ApplicationContext applicationContext) + { + if (UmbracoContext.Current == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + return; + } + service.SendNotification(entities, action, UmbracoContext.Current); + } + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext) { if (umbracoContext == null) @@ -36,6 +47,16 @@ namespace Umbraco.Web service.SendNotification(entity, action, umbracoContext, umbracoContext.Application); } + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext) + { + if (umbracoContext == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + return; + } + service.SendNotification(entities, action, umbracoContext, umbracoContext.Application); + } + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) { if (umbracoContext == null) @@ -60,11 +81,35 @@ namespace Umbraco.Web service.SendNotification(user, entity, action, umbracoContext, applicationContext); } + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + { + if (umbracoContext == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + return; + } + + var user = umbracoContext.Security.CurrentUser; + + //if there is no current user, then use the admin + if (user == null) + { + LogHelper.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = applicationContext.Services.UserService.GetUserById(0); + if (user == null) + { + LogHelper.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id 0 could be resolved"); + return; + } + } + service.SendNotification(user, entities, action, umbracoContext, applicationContext); + } + internal static void SendNotification(this INotificationService service, IUser sender, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) { - if (sender == null) throw new ArgumentNullException("sender"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (sender == null) throw new ArgumentNullException(nameof(sender)); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (applicationContext == null) throw new ArgumentNullException(nameof(applicationContext)); applicationContext.Services.NotificationService.SendNotifications( @@ -78,5 +123,24 @@ namespace Umbraco.Web ? applicationContext.Services.TextService.Localize("notifications/mailBody", mailingUser.GetUserCulture(applicationContext.Services.TextService), strings) : applicationContext.Services.TextService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(applicationContext.Services.TextService), strings)); } + + internal static void SendNotification(this INotificationService service, IUser sender, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + { + if (sender == null) throw new ArgumentNullException(nameof(sender)); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (applicationContext == null) throw new ArgumentNullException(nameof(applicationContext)); + + + applicationContext.Services.NotificationService.SendNotifications( + sender, + entities, + action.Letter.ToString(CultureInfo.InvariantCulture), + applicationContext.Services.TextService.Localize("actions", action.Alias), + umbracoContext.HttpContext, + (mailingUser, strings) => applicationContext.Services.TextService.Localize("notifications/mailSubject", mailingUser.GetUserCulture(applicationContext.Services.TextService), strings), + (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail + ? applicationContext.Services.TextService.Localize("notifications/mailBody", mailingUser.GetUserCulture(applicationContext.Services.TextService), strings) + : applicationContext.Services.TextService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(applicationContext.Services.TextService), strings)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateTimePreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimePreValueEditor.cs new file mode 100644 index 0000000000..242893cbca --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateTimePreValueEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + internal class DateTimePreValueEditor : DatePreValueEditor + { + [PreValueField("offsetTime", "Offset time", "boolean", Description = "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] + public bool OffsetTime { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs index 3925df1d23..38a4c50d6c 100644 --- a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs @@ -2,7 +2,10 @@ using System; using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -15,7 +18,11 @@ namespace Umbraco.Web.PropertyEditors { //NOTE: This is very important that we do not use .Net format's there, this format // is the correct format for the JS picker we are using so you cannot capitalize the HH, they need to be 'hh' - {"format", "YYYY-MM-DD HH:mm:ss"} + {"format", "YYYY-MM-DD HH:mm:ss"}, + //a pre-value indicating if the client/server time should be offset, when set to true the date/time seen + // by the client will be offset with the server time. + // For example, this is forced to true for scheduled publishing date/time pickers + {"offsetTime", "0"} }; } @@ -41,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors protected override PreValueEditor CreatePreValueEditor() { - return new DatePreValueEditor(); + return new DateTimePreValueEditor(); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index 04b2cf0a7b..fb6a191b1e 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -267,15 +267,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing, ICacheProvider cacheProvider) { - return xmlNode == null - ? null - : (new XmlPublishedContent(xmlNode, isPreviewing, cacheProvider, _contentTypeCache)).CreateModel(); + return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing); // fixme cacheProvider, _contentTypeCache } private IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing, ICacheProvider cacheProvider) { return xmlNodes.Cast() - .Select(xmlNode => (new XmlPublishedContent(xmlNode, isPreviewing, cacheProvider, _contentTypeCache)).CreateModel()); + .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing)); // fixme cacheProvider, _contentTypeCache } #endregion diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 5d2416c890..de9b0718e2 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -32,6 +32,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache { private readonly IMediaService _mediaService; + private readonly IUserService _userService; // by default these are null unless specified by the ctor dedicated to tests // when they are null the cache derives them from the ExamineManager, see @@ -45,12 +46,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // must be specified by the ctor private readonly ICacheProvider _cacheProvider; - public PublishedMediaCache(XmlStore xmlStore, IMediaService mediaService, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) + public PublishedMediaCache(XmlStore xmlStore, IMediaService mediaService, IUserService userService, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) : base(false) { if (mediaService == null) throw new ArgumentNullException(nameof(mediaService)); - _mediaService = mediaService; - _cacheProvider = cacheProvider; + if (userService == null) throw new ArgumentNullException(nameof(userService)); + _mediaService = mediaService; + _userService = userService; + + _cacheProvider = cacheProvider; _xmlStore = xmlStore; _contentTypeCache = contentTypeCache; } @@ -63,15 +67,17 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// /// - internal PublishedMediaCache(IMediaService mediaService, ILuceneSearcher searchProvider, BaseIndexProvider indexProvider, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) + internal PublishedMediaCache(IMediaService mediaService, IUserService userService, ILuceneSearcher searchProvider, BaseIndexProvider indexProvider, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) : base(false) { if (mediaService == null) throw new ArgumentNullException(nameof(mediaService)); - if (searchProvider == null) throw new ArgumentNullException(nameof(searchProvider)); + if (userService == null) throw new ArgumentNullException(nameof(userService)); + if (searchProvider == null) throw new ArgumentNullException(nameof(searchProvider)); if (indexProvider == null) throw new ArgumentNullException(nameof(indexProvider)); _mediaService = mediaService; - _searchProvider = searchProvider; + _userService = userService; + _searchProvider = searchProvider; _indexProvider = indexProvider; _cacheProvider = cacheProvider; _contentTypeCache = contentTypeCache; @@ -293,10 +299,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache "Could not retrieve media {0} from Examine index, reverting to looking up media via legacy library.GetMedia method", () => id); - var media = library.GetMedia(id, false); + //var media = library.GetMedia(id, false); + //return ConvertFromXPathNodeIterator(media, id); - return ConvertFromXPathNodeIterator(media, id); - } + var media = ApplicationContext.Current.Services.MediaService.GetById(id); + return media == null ? null : ConvertFromIMedia(media); + } internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id) { @@ -381,14 +389,49 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache }; } - /// - /// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists - /// in the results, if it does not, then we'll have to revert to looking up in the db. - /// - /// - /// - /// - private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias) + internal CacheValues ConvertFromIMedia(IMedia media) + { + var values = new Dictionary(); + + var creator = _userService.GetProfileById(media.CreatorId); + var creatorName = creator == null ? "" : creator.Name; + + values["id"] = media.Id.ToString(); + values["key"] = media.Key.ToString(); + values["parentID"] = media.ParentId.ToString(); + values["level"] = media.Level.ToString(); + values["creatorID"] = media.CreatorId.ToString(); + values["creatorName"] = creatorName; + values["writerID"] = media.CreatorId.ToString(); + values["writerName"] = creatorName; + values["template"] = "0"; + values["urlName"] = ""; + values["sortOrder"] = media.SortOrder.ToString(); + values["createDate"] = media.CreateDate.ToString("yyyy-MM-dd HH:mm:ss"); + values["updateDate"] = media.UpdateDate.ToString("yyyy-MM-dd HH:mm:ss"); + values["nodeName"] = media.Name; + values["path"] = media.Path; + values["nodeType"] = media.ContentType.Id.ToString(); + values["nodeTypeAlias"] = media.ContentType.Alias; + + // add the user props + foreach (var prop in media.Properties) + values[prop.Alias] = prop.Value == null ? null : prop.Value.ToString(); + + return new CacheValues + { + Values = values + }; + } + + /// + /// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists + /// in the results, if it does not, then we'll have to revert to looking up in the db. + /// + /// + /// + /// + private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias) { //lets check if the alias does not exist on the document. //NOTE: Examine will not index empty values and we do not output empty XML Elements to the cache - either of these situations @@ -605,7 +648,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _keysAdded.Add(alias); string value; const bool isPreviewing = false; // false :: never preview a media - var property = valueDictionary.TryGetValue(alias, out value) == false + var property = valueDictionary.TryGetValue(alias, out value) == false || value == null ? new XmlPublishedProperty(propertyType, isPreviewing) : new XmlPublishedProperty(propertyType, isPreviewing, value); _properties.Add(property); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 6e021e5b9d..b3d090a85a 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Xml; using System.Xml.Serialization; using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Models; @@ -29,52 +27,30 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// A value indicating whether the published content is being previewed. /// A cache provider. /// A content type cache. - public XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) + private XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) { _xmlNode = xmlNode; _isPreviewing = isPreviewing; _cacheProvider = cacheProvider; _contentTypeCache = contentTypeCache; - InitializeStructure(); - Initialize(); - InitializeChildren(); - } - - /// - /// Initializes a new instance of the XmlPublishedContent class with an Xml node, - /// and a value indicating whether to lazy-initialize the instance. - /// - /// The Xml node. - /// A value indicating whether the published content is being previewed. - /// A cache provider. - /// A content type cache. - /// A value indicating whether to lazy-initialize the instance. - /// Lazy-initializationg is NOT thread-safe. - internal XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache, bool lazyInitialize) - { - _xmlNode = xmlNode; - _isPreviewing = isPreviewing; - _cacheProvider = cacheProvider; - _contentTypeCache = contentTypeCache; - InitializeStructure(); - if (lazyInitialize == false) - { - Initialize(); - InitializeChildren(); - } } private readonly XmlNode _xmlNode; + private readonly bool _isPreviewing; private readonly ICacheProvider _cacheProvider; private readonly PublishedContentTypeCache _contentTypeCache; - - private bool _initialized; + + private bool _nodeInitialized; + private bool _parentInitialized; private bool _childrenInitialized; - private readonly ICollection _children = new Collection(); + private IEnumerable _children = Enumerable.Empty(); private IPublishedContent _parent; - private int _id; + private PublishedContentType _contentType; + private Dictionary _properties; + + private int _id; private Guid _key; private int _template; private string _name; @@ -89,28 +65,25 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private DateTime _createDate; private DateTime _updateDate; private Guid _version; - private IPublishedProperty[] _properties; private int _sortOrder; private int _level; private bool _isDraft; - private readonly bool _isPreviewing; - private PublishedContentType _contentType; public override IEnumerable Children { get { - if (_initialized == false) - Initialize(); - if (_childrenInitialized == false) - InitializeChildren(); - return _children.OrderBy(x => x.SortOrder); + if (_nodeInitialized == false) InitializeNode(); + if (_childrenInitialized == false) InitializeChildren(); + return _children; } } public override IPublishedProperty GetProperty(string alias) { - return Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + if (_nodeInitialized == false) InitializeNode(); + IPublishedProperty property; + return _properties.TryGetValue(alias, out property) ? property : null; } // override to implement cache @@ -120,24 +93,21 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { if (recurse == false) return GetProperty(alias); - var key = string.Format("XmlPublishedCache.PublishedContentCache:RecursiveProperty-{0}-{1}", Id, alias.ToLowerInvariant()); + var key = $"XmlPublishedCache.PublishedContentCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}"; var cacheProvider = _cacheProvider; return cacheProvider.GetCacheItem(key, () => base.GetProperty(alias, true)); // note: cleared by PublishedContentCache.Resync - any change here must be applied there } - public override PublishedItemType ItemType - { - get { return PublishedItemType.Content; } - } + public override PublishedItemType ItemType => PublishedItemType.Content; - public override IPublishedContent Parent + public override IPublishedContent Parent { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); + if (_parentInitialized == false) InitializeParent(); return _parent; } } @@ -146,8 +116,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _id; } } @@ -156,8 +125,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _key; } } @@ -166,8 +134,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _template; } } @@ -176,8 +143,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _sortOrder; } } @@ -186,8 +152,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _name; } } @@ -196,8 +161,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _docTypeAlias; } } @@ -206,8 +170,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _docTypeId; } } @@ -216,8 +179,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _writerName; } } @@ -226,8 +188,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _creatorName; } } @@ -236,8 +197,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _writerId; } } @@ -246,8 +206,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _creatorId; } } @@ -256,8 +215,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _path; } } @@ -266,8 +224,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _createDate; } } @@ -276,8 +233,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _updateDate; } } @@ -286,8 +242,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _version; } } @@ -296,8 +251,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _urlName; } } @@ -306,8 +260,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _level; } } @@ -316,8 +269,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _isDraft; } } @@ -326,9 +278,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); - return _properties; + if (_nodeInitialized == false) InitializeNode(); + return _properties.Values; } } @@ -336,24 +287,24 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _contentType; } } - - private void InitializeStructure() - { - // load parent if it exists and is a node - var parent = _xmlNode == null ? null : _xmlNode.ParentNode; + private void InitializeParent() + { + var parent = _xmlNode?.ParentNode; if (parent == null) return; - if (parent.Attributes != null && parent.Attributes.GetNamedItem("isDoc") != null) - _parent = (new XmlPublishedContent(parent, _isPreviewing, _cacheProvider, _contentTypeCache, true)).CreateModel(); + if (parent.Attributes?.GetNamedItem("isDoc") != null) + _parent = Get(parent, _isPreviewing, _cacheProvider, _contentTypeCache); + + // warn: this is not thread-safe... + _parentInitialized = true; } - private void Initialize() + private void InitializeNode() { if (_xmlNode == null) return; @@ -382,7 +333,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _creatorName = _writerName; } - //Added the actual userID, as a user cannot be looked up via full name only... + //Added the actual userID, as a user cannot be looked up via full name only... if (_xmlNode.Attributes.GetNamedItem("creatorID") != null) _creatorId = int.Parse(_xmlNode.Attributes.GetNamedItem("creatorID").Value); if (_xmlNode.Attributes.GetNamedItem("writerID") != null) @@ -407,7 +358,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } // load data - var dataXPath = "* [not(@isDoc)]"; + const string dataXPath = "* [not(@isDoc)]"; var nodes = _xmlNode.SelectNodes(dataXPath); _contentType = _contentTypeCache.Get(PublishedItemType.Content, _docTypeAlias); @@ -420,16 +371,19 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache propertyNodes[alias.ToLowerInvariant()] = n; } - _properties = _contentType.PropertyTypes.Select(p => - { - XmlNode n; - return propertyNodes.TryGetValue(p.PropertyTypeAlias.ToLowerInvariant(), out n) - ? new XmlPublishedProperty(p, _isPreviewing, n) - : new XmlPublishedProperty(p, _isPreviewing); - }).Cast().ToArray(); + _properties = _contentType.PropertyTypes.Select(p => + { + XmlNode n; + return propertyNodes.TryGetValue(p.PropertyTypeAlias.ToLowerInvariant(), out n) + ? new XmlPublishedProperty(p, _isPreviewing, n) + : new XmlPublishedProperty(p, _isPreviewing); + }).Cast().ToDictionary( + x => x.PropertyTypeAlias, + x => x, + StringComparer.OrdinalIgnoreCase); // warn: this is not thread-safe... - _initialized = true; + _nodeInitialized = true; } private void InitializeChildren() @@ -437,17 +391,49 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (_xmlNode == null) return; // load children - var childXPath = "* [@isDoc]"; + const string childXPath = "* [@isDoc]"; var nav = _xmlNode.CreateNavigator(); var expr = nav.Compile(childXPath); - expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); + //expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); var iterator = nav.Select(expr); - while (iterator.MoveNext()) - _children.Add( - (new XmlPublishedContent(((IHasXmlNode)iterator.Current).GetNode(), _isPreviewing, _cacheProvider, _contentTypeCache, true)).CreateModel()); - + + _children = iterator.Cast() + .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _cacheProvider, _contentTypeCache)) + .OrderBy(x => x.SortOrder) + .ToList(); + // warn: this is not thread-safe _childrenInitialized = true; } - } + + /// + /// Gets an IPublishedContent corresponding to an Xml cache node. + /// + /// The Xml node. + /// A value indicating whether we are previewing or not. + /// A cache provider. + /// A content type cache. + /// The IPublishedContent corresponding to the Xml cache node. + /// Maintains a per-request cache of IPublishedContent items in order to make + /// sure that we create only one instance of each for the duration of a request. The + /// returned IPublishedContent is a model, if models are enabled. + public static IPublishedContent Get(XmlNode node, bool isPreviewing, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) + { + // only 1 per request + + var attrs = node.Attributes; + var id = attrs?.GetNamedItem("id").Value; + if (id.IsNullOrWhiteSpace()) throw new InvalidOperationException("Node has no ID attribute."); + var cache = ApplicationContext.Current.ApplicationCache.RequestCache; + var key = CacheKeyPrefix + id; // dont bother with preview, wont change during request in v7 + return (IPublishedContent) cache.GetCacheItem(key, () => (new XmlPublishedContent(node, isPreviewing, cacheProvider, contentTypeCache)).CreateModel()); + } + + public static void ClearRequest() + { + ApplicationContext.Current.ApplicationCache.RequestCache.ClearCacheByKeySearch(CacheKeyPrefix); + } + + private const string CacheKeyPrefix = "CONTENTCACHE_XMLPUBLISHEDCONTENT_"; + } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs index 6d8e9b0229..6326d13ca3 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs @@ -2,10 +2,8 @@ using System; using System.Xml; using System.Xml.Serialization; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; -using Umbraco.Web.Models; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -18,10 +16,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal class XmlPublishedProperty : PublishedPropertyBase { private readonly string _xmlValue; // the raw, xml node value - private readonly Lazy _sourceValue; - private readonly Lazy _objectValue; - private readonly Lazy _xpathValue; - private readonly bool _isPreviewing; + + // in v7 we're not using XPath value so don't allocate that Lazy. + // as for the rest... we're single threaded here, keep it simple + //private readonly Lazy _sourceValue; + //private readonly Lazy _objectValue; + //private readonly Lazy _xpathValue; + private object _objectValue; + private bool _objectValueComputed; + private readonly bool _isPreviewing; /// /// Gets the raw value of the property. @@ -35,10 +38,26 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache get { return _xmlValue.Trim().Length > 0; } } - public override object Value { get { return _objectValue.Value; } } - public override object XPathValue { get { return _xpathValue.Value; } } + public override object Value + { + get + { + // NOT caching the source (intermediate) value since we'll never need it + // everything in Xml cache in v7 is per-request anyways + // also, properties should not be shared between requests and therefore + // are single threaded, so the following code should be safe & fast - public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, XmlNode propertyXmlData) + if (_objectValueComputed) return _objectValue; + var sourceValue = PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing); + _objectValue = PropertyType.ConvertSourceToObject(sourceValue, _isPreviewing); + _objectValueComputed = true; + return _objectValue; + } + } + + public override object XPathValue { get { throw new NotImplementedException(); } } + + public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, XmlNode propertyXmlData) : this(propertyType, isPreviewing) { if (propertyXmlData == null) @@ -60,9 +79,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _xmlValue = string.Empty; _isPreviewing = isPreviewing; - _sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing)); - _objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _isPreviewing)); - _xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreviewing)); + //_sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing)); + //_objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _isPreviewing)); + //_xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreviewing)); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs new file mode 100644 index 0000000000..6e1d4b3a1b --- /dev/null +++ b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs @@ -0,0 +1,193 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Events; +using System.Collections.Generic; +using Umbraco.Core.Cache; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; + +namespace Umbraco.Web.Redirects +{ + /// + /// Implements an Application Event Handler for managing redirect urls tracking. + /// + /// + /// when content is renamed or moved, we want to create a permanent 301 redirect from it's old url + /// not managing domains because we don't know how to do it - changing domains => must create a higher level strategy using rewriting rules probably + /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same + /// + public class RedirectTrackingEventHandler : ApplicationEventHandler + { + private const string ContextKey1 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.1"; + private const string ContextKey2 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.2"; + private const string ContextKey3 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.3"; + + /// + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + // events are weird + // on 'published' we 'could' get the old or the new route depending on event handlers order + // so it is not reliable. getting the old route in 'publishing' to be sure and storing in http + // context. then for the same reason, we have to process these old items only when the cache + // is ready + // when moving, the moved node is also published, which is causing all sorts of troubles with + // descendants, so when moving, we lock events so that neither 'published' nor 'publishing' + // are processed more than once + // we cannot rely only on ContentCacheRefresher because when CacheUpdated triggers the old + // route is gone + // + // this is all verrrry weird but it seems to work + + ContentService.Publishing += ContentService_Publishing; + ContentService.Published += ContentService_Published; + ContentService.Moving += ContentService_Moving; + ContentService.Moved += ContentService_Moved; + ContentCacheRefresher.CacheUpdated += ContentCacheRefresher_CacheUpdated; + + // kill all redirects once a content is deleted + //ContentService.Deleted += ContentService_Deleted; + // BUT, doing it here would prevent content deletion due to FK + // so the rows are actually deleted by the ContentRepository (see GetDeleteClauses) + + // rolled back items have to be published, so publishing will take care of that + } + + private static void ContentCacheRefresher_CacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) + { + // sanity checks + + if (args.MessageType != MessageType.RefreshByPayload) + throw new InvalidOperationException("ContentCacheRefresher MessageType should be ByPayload."); + + if (args.MessageObject == null) return; + var payloads = args.MessageObject as ContentCacheRefresher.JsonPayload[]; + if (payloads == null) + throw new InvalidOperationException("ContentCacheRefresher MessageObject should be JsonPayload[]."); + + // manage routes + + var removeKeys = new List(); + + foreach (var oldRoute in OldRoutes) + { + // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need + // to set a flag in 'Published' to indicate which entities have been refreshed ok + CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2); + removeKeys.Add(oldRoute.Key); + } + + foreach (var k in removeKeys) + OldRoutes.Remove(k); + } + + private static Dictionary> OldRoutes + { + get + { + var oldRoutes = (Dictionary>) UmbracoContext.Current.HttpContext.Items[ContextKey3]; + if (oldRoutes == null) + UmbracoContext.Current.HttpContext.Items[ContextKey3] = oldRoutes = new Dictionary>(); + return oldRoutes; + } + } + + private static bool LockedEvents + { + get { return Moving && UmbracoContext.Current.HttpContext.Items[ContextKey2] != null; } + set + { + if (Moving && value) + UmbracoContext.Current.HttpContext.Items[ContextKey2] = true; + else + UmbracoContext.Current.HttpContext.Items.Remove(ContextKey2); + } + } + + private static bool Moving + { + get { return UmbracoContext.Current.HttpContext.Items[ContextKey1] != null; } + set + { + if (value) + UmbracoContext.Current.HttpContext.Items[ContextKey1] = true; + else + { + UmbracoContext.Current.HttpContext.Items.Remove(ContextKey1); + UmbracoContext.Current.HttpContext.Items.Remove(ContextKey2); + } + } + } + + private static void ContentService_Publishing(IContentService sender, PublishEventArgs args) + { + if (LockedEvents) return; + + var contentCache = UmbracoContext.Current.ContentCache; + foreach (var entity in args.PublishedEntities) + { + var entityContent = contentCache.GetById(entity.Id); + if (entityContent == null) continue; + foreach (var x in entityContent.DescendantsOrSelf()) + { + var route = contentCache.GetRouteById(x.Id); + if (IsNotRoute(route)) continue; + var wk = UnwrapToKey(x); + if (wk == null) continue; + OldRoutes[x.Id] = Tuple.Create(wk.Key, route); + } + } + + LockedEvents = true; // we only want to see the "first batch" + } + + private static IPublishedContentWithKey UnwrapToKey(IPublishedContent content) + { + if (content == null) return null; + var withKey = content as IPublishedContentWithKey; + if (withKey != null) return withKey; + + var extended = content as PublishedContentExtended; + while (extended != null) + extended = (content = extended.Unwrap()) as PublishedContentExtended; + + withKey = content as IPublishedContentWithKey; + return withKey; + } + + private static void ContentService_Published(IContentService sender, PublishEventArgs e) + { + // look note in CacheUpdated + // we might want to set a flag on the entities we are seeing here + } + + private static void ContentService_Moving(IContentService sender, MoveEventArgs e) + { + Moving = true; + } + + private static void ContentService_Moved(IContentService sender, MoveEventArgs e) + { + Moving = false; + LockedEvents = false; + } + + private static void CreateRedirect(int contentId, Guid contentKey, string oldRoute) + { + var contentCache = UmbracoContext.Current.ContentCache; + var newRoute = contentCache.GetRouteById(contentId); + if (IsNotRoute(newRoute) || oldRoute == newRoute) return; + var redirectUrlService = ApplicationContext.Current.Services.RedirectUrlService; + redirectUrlService.Register(oldRoute, contentKey); + } + + private static bool IsNotRoute(string route) + { + // null if content not found + // err/- if collision or anomaly or ... + return route == null || route.StartsWith("err/"); + } + } +} diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs new file mode 100644 index 0000000000..86a06253f6 --- /dev/null +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -0,0 +1,51 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Routing +{ + /// + /// Provides an implementation of that handles page url rewrites + /// that are stored when moving, saving, or deleting a node. + /// + /// + /// Assigns a permanent redirect notification to the request. + /// + public class ContentFinderByRedirectUrl : IContentFinder + { + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + /// Optionally, can also assign the template or anything else on the document request, although that is not required. + public bool TryFindContent(PublishedContentRequest contentRequest) + { + var route = contentRequest.HasDomain + ? contentRequest.Domain.ContentId + DomainHelper.PathRelativeToDomain(contentRequest.DomainUri, contentRequest.Uri.GetAbsolutePathDecoded()) + : contentRequest.Uri.GetAbsolutePathDecoded(); + + var service = contentRequest.RoutingContext.UmbracoContext.Application.Services.RedirectUrlService; + var redirectUrl = service.GetMostRecentRedirectUrl(route); + + if (redirectUrl == null) + { + LogHelper.Debug("No match for route: \"{0}\".", () => route); + return false; + } + + var content = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectUrl.ContentId); + var url = content == null ? "#" : content.Url; + if (url.StartsWith("#")) + { + LogHelper.Debug("Route \"{0}\" matches content {1} which has no url.", + () => route, () => redirectUrl.ContentId); + return false; + } + + LogHelper.Debug("Route \"{0}\" matches content {1} with url \"{2}\", redirecting.", + () => route, () => content.Id, () => url); + contentRequest.SetRedirectPermanent(url); + return true; + } + } +} diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index 4e65d35ecc..bfb8c11a64 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -154,10 +154,10 @@ namespace Umbraco.Web.Security.Identity /// public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, ApplicationContext appContext, PipelineStage stage) { - if (app == null) throw new ArgumentNullException("app"); - if (appContext == null) throw new ArgumentNullException("appContext"); + //Create the default options and provider + var authOptions = app.CreateUmbracoCookieAuthOptions(); - var cookieAuthProvider = new BackOfficeCookieAuthenticationProvider + authOptions.Provider = new BackOfficeCookieAuthenticationProvider { // Enables the application to validate the security stamp when the user // logs in. This is a security feature which is used when you @@ -167,20 +167,39 @@ namespace Umbraco.Web.Security.Identity TimeSpan.FromMinutes(30), (manager, user) => user.GenerateUserIdentityAsync(manager), identity => identity.GetUserId()), + }; - var authOptions = CreateCookieAuthOptions(); - authOptions.Provider = cookieAuthProvider; + return app.UseUmbracoBackOfficeCookieAuthentication(appContext, authOptions, stage); + } - app.UseUmbracoBackOfficeCookieAuthentication(authOptions, appContext, stage); + /// + /// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline + /// + /// + /// + /// Custom auth cookie options can be specified to have more control over the cookie authentication logic + /// + /// Configurable pipeline stage + /// + /// + public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, ApplicationContext appContext, CookieAuthenticationOptions cookieOptions, PipelineStage stage) + { + if (app == null) throw new ArgumentNullException("app"); + if (appContext == null) throw new ArgumentNullException("appContext"); + if (cookieOptions == null) throw new ArgumentNullException("cookieOptions"); + if (cookieOptions.Provider == null) throw new ArgumentNullException("cookieOptions.Provider"); + if ((cookieOptions.Provider is BackOfficeCookieAuthenticationProvider) == false) throw new ArgumentException("The cookieOptions.Provider must be of type " + typeof(BackOfficeCookieAuthenticationProvider)); + + app.UseUmbracoBackOfficeCookieAuthenticationInternal(cookieOptions, appContext, stage); - //don't apply if app isnot ready + //don't apply if app is not ready if (appContext.IsUpgrading || appContext.IsConfigured) { - var getSecondsOptions = CreateCookieAuthOptions( + var getSecondsOptions = app.CreateUmbracoCookieAuthOptions( //This defines the explicit path read cookies from for this middleware new[] {string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path)}); - getSecondsOptions.Provider = cookieAuthProvider; + getSecondsOptions.Provider = cookieOptions.Provider; //This is a custom middleware, we need to return the user's remaining logged in seconds app.Use( @@ -192,7 +211,7 @@ namespace Umbraco.Web.Security.Identity return app; } - internal static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options, ApplicationContext appContext, PipelineStage stage = PipelineStage.Authenticate) + private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, ApplicationContext appContext, PipelineStage stage) { if (app == null) { @@ -210,9 +229,7 @@ namespace Umbraco.Web.Security.Identity } //Marks all of the above middlewares to execute on Authenticate - app.UseStageMarker(stage); - - return app; + app.UseStageMarker(stage); } @@ -295,7 +312,7 @@ namespace Umbraco.Web.Security.Identity //don't apply if app isnot ready if (appContext.IsConfigured) { - var authOptions = CreateCookieAuthOptions(); + var authOptions = app.CreateUmbracoCookieAuthOptions(); app.Use(typeof(PreviewAuthenticationMiddleware), authOptions); //This middleware must execute at least on PostAuthentication, by default it is on Authorize @@ -322,9 +339,10 @@ namespace Umbraco.Web.Security.Identity /// /// Create the default umb cookie auth options /// + /// /// /// - private static UmbracoBackOfficeCookieAuthOptions CreateCookieAuthOptions(string[] explicitPaths = null) + public static UmbracoBackOfficeCookieAuthOptions CreateUmbracoCookieAuthOptions(this IAppBuilder app, string[] explicitPaths = null) { var authOptions = new UmbracoBackOfficeCookieAuthOptions( explicitPaths, diff --git a/src/Umbraco.Web/Strategies/NotificationsHandler.cs b/src/Umbraco.Web/Strategies/NotificationsHandler.cs index 1dc1a02418..8f51104661 100644 --- a/src/Umbraco.Web/Strategies/NotificationsHandler.cs +++ b/src/Umbraco.Web/Strategies/NotificationsHandler.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; using umbraco; using Umbraco.Web._Legacy.Actions; +using Umbraco.Core.Models; namespace Umbraco.Web.Strategies { @@ -36,6 +37,9 @@ namespace Umbraco.Web.Strategies //Send notifications for the update and created actions ContentService.Saved += (sender, args) => { + var newEntities = new List(); + var updatedEntities = new List(); + //need to determine if this is updating or if it is new foreach (var entity in args.SavedEntities) { @@ -43,16 +47,16 @@ namespace Umbraco.Web.Strategies if (dirty.WasPropertyDirty("Id")) { //it's new - applicationContext.Services.NotificationService.SendNotification( - entity, ActionNew.Instance, applicationContext); + newEntities.Add(entity); } else { //it's updating - applicationContext.Services.NotificationService.SendNotification( - entity, ActionUpdate.Instance, applicationContext); + updatedEntities.Add(entity); } } + applicationContext.Services.NotificationService.SendNotification(newEntities, ActionNew.Instance, applicationContext); + applicationContext.Services.NotificationService.SendNotification(updatedEntities, ActionUpdate.Instance, applicationContext); }; //Send notifications for the delete action diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 88abf40e71..8901fa8134 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -19,7 +19,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] - [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, null, sortOrder:7)] + [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, null, sortOrder:1)] [PluginController("UmbracoTrees")] [CoreTree] public class DataTypeTreeController : TreeController diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs new file mode 100644 index 0000000000..5f4a7796df --- /dev/null +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using umbraco; +using umbraco.cms.businesslogic.packager; +using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Packages)] + [Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)] + [PluginController("UmbracoTrees")] + [CoreTree] + [LegacyBaseTree(typeof(loadPackager))] + public class PackagesTreeController : TreeController + { + /// + /// Helper method to create a root model for a tree + /// + /// + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Developer, Constants.Trees.Packages, "overview"); + root.Icon = "icon-box"; + + return root; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var baseUrl = Constants.Applications.Developer + "/packages/"; + + var nodes = new TreeNodeCollection(); + + var createdPackages = CreatedPackage.GetAllCreatedPackages(); + + if (id == "created") + { + nodes.AddRange( + createdPackages + .OrderBy(entity => entity.Data.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false, + string.Format("/{0}/framed/{1}", + queryStrings.GetValue("application"), + Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id))); + return node; + })); + } + else + { + //must be root + var node = CreateTreeNode( + "created", + id, + queryStrings, + Services.TextService.Localize("treeHeaders/createdPackages"), + "icon-folder", + createdPackages.Count > 0, + string.Empty); + + + + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + + nodes.Add(node); + } + + + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + // Root actions + if (id == "-1") + { + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) + .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); + } + else if (id == "created") + { + menu.Items.Add( + Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + } + else + { + //it's a package node + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); + } + + return menu; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs b/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs new file mode 100644 index 0000000000..0d546d6a42 --- /dev/null +++ b/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.UserPermissions)] + [Tree(Constants.Applications.Users, Constants.Trees.UserPermissions, null, sortOrder: 2)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class UserPermissionsTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + long totalUsers; + nodes.AddRange( + Services.UserService.GetAll(0, int.MaxValue, out totalUsers) + .Where(x => x.Id > 0 && x.IsApproved) + .Select(x => CreateTreeNode(x.Id.ToString(), + id, + queryStrings, + x.Name, + "icon-user", + false, + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("users/PermissionEditor.aspx?id=" + x.Id)))); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + // root actions + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + return menu; + } + + return menu; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 32f8e3751d..e27b8561d1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -170,11 +170,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -205,6 +232,7 @@ + @@ -255,18 +283,22 @@ + + + + @@ -277,14 +309,18 @@ + + + + @@ -301,6 +337,7 @@ + @@ -619,6 +656,7 @@ + @@ -1388,7 +1426,6 @@ - XmlTree.xsd diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index aee1c80db2..8a7efa5077 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1058,66 +1058,121 @@ namespace Umbraco.Web return text.ToMd5(); } + /// + /// Strips all html tags from a given string, all contents of the tags will remain. + /// public HtmlString StripHtml(IHtmlString html, params string[] tags) { return StripHtml(html.ToHtmlString(), tags); } + + /// + /// Strips all html tags from a given string, all contents of the tags will remain. + /// public HtmlString StripHtml(DynamicNull html, params string[] tags) { return new HtmlString(string.Empty); } + + /// + /// Strips all html tags from a given string, all contents of the tags will remain. + /// public HtmlString StripHtml(string html, params string[] tags) { return _stringUtilities.StripHtmlTags(html, tags); } - + + /// + /// Will take the first non-null value in the collection and return the value of it. + /// public string Coalesce(params object[] args) { return _stringUtilities.Coalesce(args); } + /// + /// Will take the first non-null value in the collection and return the value of it. + /// public string Concatenate(params object[] args) { return _stringUtilities.Concatenate(args); } + /// + /// Joins any number of int/string/objects into one string and seperates them with the string seperator parameter. + /// public string Join(string seperator, params object[] args) { return _stringUtilities.Join(seperator, args); } + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(IHtmlString html, int length) { return Truncate(html.ToHtmlString(), length, true, false); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis) { return Truncate(html.ToHtmlString(), length, addElipsis, false); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis, bool treatTagsAsContent) { return Truncate(html.ToHtmlString(), length, addElipsis, treatTagsAsContent); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(DynamicNull html, int length) { return new HtmlString(string.Empty); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(DynamicNull html, int length, bool addElipsis) { return new HtmlString(string.Empty); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(DynamicNull html, int length, bool addElipsis, bool treatTagsAsContent) { return new HtmlString(string.Empty); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(string html, int length) { return Truncate(html, length, true, false); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(string html, int length, bool addElipsis) { return Truncate(html, length, addElipsis, false); } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) { return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); @@ -1128,10 +1183,17 @@ namespace Umbraco.Web #region If + /// + /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. + /// public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) { return test ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); } + + /// + /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. + /// public HtmlString If(bool test, string valueIfTrue) { return test ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs index 3836d90fd7..a4d4116174 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs @@ -172,7 +172,7 @@ namespace Umbraco.Web.WebApi.Binders //create the dto from the persisted model if (model.PersistedContent != null) { - model.ContentDto = MapFromPersisted(model); + model.ContentDto = MapFromPersisted(model); } if (model.ContentDto != null) { @@ -192,9 +192,15 @@ namespace Umbraco.Web.WebApi.Binders /// private static void MapPropertyValuesFromSaved(TModelSave saveModel, ContentItemDto dto) { - foreach (var p in saveModel.Properties.Where(p => dto.Properties.Any(x => x.Alias == p.Alias))) + //NOTE: Don't convert this to linq, this is much quicker + foreach (var p in saveModel.Properties) { - dto.Properties.Single(x => x.Alias == p.Alias).Value = p.Value; + foreach (var propertyDto in dto.Properties) + { + if (propertyDto.Alias != p.Alias) continue; + propertyDto.Value = p.Value; + break; + } } } diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoUseHttps.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoUseHttps.cs index 2cd674377b..2e6647ea06 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoUseHttps.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoUseHttps.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.ComponentModel; +using System.Linq; using System.Web.Http; using System.Web.Http.Controllers; using Umbraco.Core; @@ -7,37 +9,9 @@ using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Web.WebApi.Filters { - /// - /// If umbracoUseSSL property in web.config is set to true, this filter will redirect any http access to https. - /// - public class UmbracoUseHttps : RequireHttpsAttribute + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the filter Umbraco.Web.Mvc.UmbracoRequireHttpsAttribute instead, this one is in the wrong namespace")] + public class UmbracoUseHttps : Umbraco.Web.Mvc.UmbracoRequireHttpsAttribute { - /// - /// If umbracoUseSSL is true and we have a non-HTTPS request, handle redirect. - /// - /// Filter context - protected override void HandleNonHttpsRequest(AuthorizationContext filterContext) - { - // If umbracoUseSSL is set, let base method handle redirect. Otherwise, we don't care. - if (GlobalSettings.UseSSL) - { - base.HandleNonHttpsRequest(filterContext); - } - } - - /// - /// Check to see if HTTPS is currently being used if umbracoUseSSL is true. - /// - /// Filter context - public override void OnAuthorization(AuthorizationContext filterContext) - { - // If umbracoSSL is set, let base method handle checking for HTTPS. Otherwise, we don't care. - if (GlobalSettings.UseSSL) - { - base.OnAuthorization(filterContext); - } - } - - } } diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs new file mode 100644 index 0000000000..f292a63a20 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs @@ -0,0 +1,55 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// If umbracoUseSSL property in web.config is set to true, this filter will redirect any http access to https. + /// + /// + /// This will only redirect Head/Get requests, otherwise will respond with text + /// + /// References: + /// http://issues.umbraco.org/issue/U4-8542 + /// https://blogs.msdn.microsoft.com/carlosfigueira/2012/03/09/implementing-requirehttps-with-asp-net-web-api/ + /// + public class UmbracoWebApiRequireHttpsAttribute : AuthorizationFilterAttribute + { + public override void OnAuthorization(HttpActionContext actionContext) + { + var request = actionContext.Request; + if (GlobalSettings.UseSSL && request.RequestUri.Scheme != Uri.UriSchemeHttps) + { + HttpResponseMessage response; + var uri = new UriBuilder(request.RequestUri) + { + Scheme = Uri.UriSchemeHttps, + Port = 443 + }; + var body = string.Format("

The resource can be found at {0}.

", + uri.Uri.AbsoluteUri); + if (request.Method.Equals(HttpMethod.Get) || request.Method.Equals(HttpMethod.Head)) + { + response = request.CreateResponse(HttpStatusCode.Found); + response.Headers.Location = uri.Uri; + if (request.Method.Equals(HttpMethod.Get)) + { + response.Content = new StringContent(body, Encoding.UTF8, "text/html"); + } + } + else + { + response = request.CreateResponse(HttpStatusCode.NotFound); + response.Content = new StringContent(body, Encoding.UTF8, "text/html"); + } + + actionContext.Response = response; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index 04035c0017..00b66094e8 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Web.Http.ModelBinding; +using System.Web.Http.Results; using Microsoft.Owin; using Umbraco.Core; using Umbraco.Web.Models.ContentEditing; @@ -124,6 +125,22 @@ namespace Umbraco.Web.WebApi return request.CreateValidationErrorResponse(notificationModel); } + /// + /// Creates a succressful response with notifications in the result to be displayed in the UI + /// + /// + /// + /// + public static HttpResponseMessage CreateNotificationSuccessResponse(this HttpRequestMessage request, string successMessage) + { + var notificationModel = new SimpleNotificationModel + { + Message = successMessage + }; + notificationModel.AddSuccessNotification(successMessage, string.Empty); + return request.CreateResponse(HttpStatusCode.OK, notificationModel); + } + /// /// Create a 400 response message indicating that a validation error occurred /// diff --git a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs index 67adea187f..b54d6102c0 100644 --- a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs +++ b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs @@ -1,5 +1,8 @@ using System; +using System.Linq; +using System.Net.Http.Formatting; using System.Web.Http.Controllers; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Umbraco.Web.WebApi @@ -11,7 +14,21 @@ namespace Umbraco.Web.WebApi { public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) { - controllerSettings.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + //remove all json formatters then add our custom one + var toRemove = controllerSettings.Formatters.Where(t => (t is JsonMediaTypeFormatter)).ToList(); + foreach (var r in toRemove) + { + controllerSettings.Formatters.Remove(r); + } + + var jsonFormatter = new JsonMediaTypeFormatter + { + SerializerSettings = + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + } + }; + controllerSettings.Formatters.Add(jsonFormatter); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 66d8278ae7..6f9abcbdcb 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -19,6 +19,7 @@ namespace Umbraco.Web.WebApi [UmbracoUserTimeoutFilter] [UmbracoAuthorize] [DisableBrowserCache] + [UmbracoWebApiRequireHttps] public abstract class UmbracoAuthorizedApiController : UmbracoApiController { protected UmbracoAuthorizedApiController() diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index b10fadcdd8..8fef4716e5 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -42,6 +42,7 @@ using Umbraco.Core.Services.Changes; using Umbraco.Core.Strings; using Umbraco.Web.Cache; using Umbraco.Web.DependencyInjection; +using Umbraco.Web.HealthCheck; using Umbraco.Web._Legacy.Actions; using UmbracoExamine; using Action = System.Action; @@ -500,8 +501,8 @@ namespace Umbraco.Web typeof(ContentFinderByIdPath), typeof(ContentFinderByNiceUrlAndTemplate), typeof(ContentFinderByProfile), - typeof(ContentFinderByUrlAlias) - + typeof(ContentFinderByUrlAlias), + typeof(ContentFinderByRedirectUrl) ); SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(Container, typeof(SiteDomainHelper)); @@ -515,6 +516,9 @@ namespace Umbraco.Web PluginManager.ResolveImageUrlProviders()); CultureDictionaryFactoryResolver.Current = new CultureDictionaryFactoryResolver(Container, typeof(DefaultCultureDictionaryFactory)); + + HealthCheckResolver.Current = new HealthCheckResolver(ProfilingLogger.Logger, + () => PluginManager.ResolveTypes()); } /// diff --git a/src/Umbraco.Web/app.config b/src/Umbraco.Web/app.config index 00dbc98091..1fcc390202 100644 --- a/src/Umbraco.Web/app.config +++ b/src/Umbraco.Web/app.config @@ -77,7 +77,7 @@ - + diff --git a/src/Umbraco.Web/project.json b/src/Umbraco.Web/project.json index 8912a26d41..05fb31971b 100644 --- a/src/Umbraco.Web/project.json +++ b/src/Umbraco.Web/project.json @@ -4,7 +4,7 @@ "ClientDependency": "1.8.*", "dotless": "1.4.1.0", "Examine": "2.0.0-beta2", - "HtmlAgilityPack": "1.4.*", + "HtmlAgilityPack": "1.4.9.5", "LightInject": "4.0.*", "LightInject.Annotation": "1.0.*", "LightInject.Mvc": "1.0.*", @@ -30,7 +30,7 @@ "Microsoft.Web.Infrastructure": "1.0.0.0", "MiniProfiler": "3.2.*", "Newtonsoft.Json": "8.0.*", - "NPoco": "3.3.3", + "NPoco": "3.3.4", "Owin": "1.0.*", "semver": "1.*", "SharpZipLib": "0.86.0", @@ -38,10 +38,10 @@ "xmlrpcnet": "2.5.0" }, "frameworks": { - "net461": { } + "net461": {} }, "runtimes": { - "win-anycpu": { }, - "win": { } + "win-anycpu": {}, + "win": {} } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserPermissions.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserPermissions.cs deleted file mode 100644 index c52326e80d..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserPermissions.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Collections.Generic; -using System.Text; -using Umbraco.Core; -using Umbraco.Web.Trees; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - [Tree(Constants.Applications.Users, "userPermissions", "User Permissions", sortOrder: 2)] - public class UserPermissions : BaseTree - { - - public UserPermissions(string application) : base(application) { } - - /// - /// don't allow any actions on this tree - /// - /// - protected override void CreateAllowedActions(ref List actions) - { - actions.Clear(); - } - - /// - /// no actions should be able to be performed on the parent node except for refresh - /// - /// - protected override void CreateRootNodeActions(ref List actions) - { - actions.Clear(); - actions.Add(ActionRefresh.Instance); - } - - public override void Render(ref XmlTree tree) - { - long totalusers; - foreach (var user in Services.UserService.GetAll(0, int.MaxValue, out totalusers)) - { - if (user.Id > 0 && user.IsApproved) - { - XmlTreeNode node = XmlTreeNode.Create(this); - node.NodeID = user.Id.ToString(); - node.Text = user.Name; - node.Action = "javascript:openUserPermissions('" + user.Id + "');"; - node.Icon = "icon-users"; - - OnBeforeNodeRender(ref tree, ref node, EventArgs.Empty); - if (node != null) - { - tree.Add(node); - OnAfterNodeRender(ref tree, ref node, EventArgs.Empty); - } - - } - } - } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.Text = Services.TextService.Localize("user/userPermissions"); - } - - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" -function openUserPermissions(id) { - UmbClientMgr.contentFrame('users/PermissionEditor.aspx?id=' + id); -} -"); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs index 53afe97527..6632bd9881 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs @@ -12,7 +12,8 @@ namespace umbraco /// /// Handles loading of the packager application into the developer application tree /// - [Tree(Constants.Applications.Developer, "packager", "Packages", sortOrder: 3)] + //[Tree(Constants.Applications.Developer, "packager", "Packages", sortOrder: 3)] + [Obsolete("This is no longer used and will be removed from the codebase in the future")] public class loadPackager : BaseTree { #region TreeI Members diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs index 9980e516a0..c80a66b533 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs @@ -13,7 +13,8 @@ using Umbraco.Web._Legacy.Actions; namespace umbraco { - [Tree(Constants.Applications.Developer, "packagerPackages", "Packager Packages", initialize: false, sortOrder: 1)] + //[Tree(Constants.Applications.Developer, "packagerPackages", "Packager Packages", initialize: false, sortOrder: 1)] + [Obsolete("This is no longer used and will be removed from the codebase in the future")] public class loadPackages : BaseTree { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/stylesheetPropertyTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/stylesheetPropertyTasks.cs index 438b4b6e5a..cf2efe7f55 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/stylesheetPropertyTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/stylesheetPropertyTasks.cs @@ -10,16 +10,16 @@ namespace umbraco { public class stylesheetPropertyTasks : LegacyDialogTask { - public override bool PerformSave() { var stylesheetName = AdditionalValues["nodeId"].ToString(); - - var s = Umbraco.Core.ApplicationContext.Current.Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); + + var s = ApplicationContext.Current.Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); s.AddProperty(new StylesheetProperty(Alias, "." + Alias.ToSafeAlias(), "")); - Umbraco.Core.ApplicationContext.Current.Services.FileService.SaveStylesheet(s); + ApplicationContext.Current.Services.FileService.SaveStylesheet(s); - _returnUrl = string.Format("settings/stylesheet/property/EditStyleSheetProperty.aspx?id={0}&prop={1}", HttpUtility.UrlEncode(s.Path), Alias); + // SJ - Note: The Alias is NOT in fact the alias but the name of the new property, need to UrlEncode it! + _returnUrl = string.Format("settings/stylesheet/property/EditStyleSheetProperty.aspx?id={0}&prop={1}", HttpUtility.UrlEncode(s.Path), HttpUtility.UrlEncode(Alias)); return true; } @@ -27,15 +27,16 @@ namespace umbraco { var parts = Alias.Split('_'); - var stylesheet = Umbraco.Core.ApplicationContext.Current.Services.FileService.GetStylesheetByName(parts[0].EnsureEndsWith(".css")); + var stylesheet = ApplicationContext.Current.Services.FileService.GetStylesheetByName(parts[0].EnsureEndsWith(".css")); if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name: " + parts[0]); - var prop = stylesheet.Properties.FirstOrDefault(x => x.Name == parts[1]); - if (prop == null) throw new InvalidOperationException("No stylesheet property found by name: " + parts[1]); + var property = HttpUtility.UrlDecode(parts[1]); + var prop = stylesheet.Properties.FirstOrDefault(x => x.Name == property); + if (prop == null) throw new InvalidOperationException("No stylesheet property found by name: " + property); stylesheet.RemoveProperty(prop.Name); - Umbraco.Core.ApplicationContext.Current.Services.FileService.SaveStylesheet(stylesheet); + ApplicationContext.Current.Services.FileService.SaveStylesheet(stylesheet); return true; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs index 5bc6784c86..50f1d2b55b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs @@ -70,8 +70,8 @@ namespace umbraco.presentation.developer.packages if (Page.IsPostBack == false) { ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree("-1,init," + loadPackages.PACKAGE_TREE_PREFIX + createdPackage.Data.Id, false); + .SetActiveTreeType(Constants.Trees.Packages) + .SyncTree("-1,created," + createdPackage.Data.Id, false); packageAuthorName.Text = pack.Author; packageAuthorUrl.Text = pack.AuthorUrl; @@ -81,6 +81,8 @@ namespace umbraco.presentation.developer.packages packageReadme.Text = pack.Readme; packageVersion.Text = pack.Version; packageUrl.Text = pack.Url; + iconUrl.Text = pack.IconUrl; + umbracoVersion.Text = pack.UmbracoVersion != null ? pack.UmbracoVersion.ToString(3) : string.Empty; /*ACTIONS XML*/ tb_actions.Text = pack.Actions; @@ -190,8 +192,8 @@ namespace umbraco.presentation.developer.packages else { ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree("-1,init," + loadPackages.PACKAGE_TREE_PREFIX + createdPackage.Data.Id, true); + .SetActiveTreeType(Constants.Trees.Packages) + .SyncTree("-1,created," + createdPackage.Data.Id, true); } } } @@ -260,6 +262,8 @@ namespace umbraco.presentation.developer.packages pack.Name = packageName.Text; pack.Url = packageUrl.Text; pack.Version = packageVersion.Text; + pack.IconUrl = iconUrl.Text; + pack.UmbracoVersion = Version.Parse(umbracoVersion.Text); pack.ContentLoadChildNodes = packageContentSubdirs.Checked; @@ -381,9 +385,11 @@ namespace umbraco.presentation.developer.packages // Tab setup packageInfo = TabView1.NewTabPage("Package Properties"); packageInfo.Controls.Add(Pane1); + packageInfo.Controls.Add(Pane5); packageInfo.Controls.Add(Pane1_1); packageInfo.Controls.Add(Pane1_2); packageInfo.Controls.Add(Pane1_3); + packageContents = TabView1.NewTabPage("Package Contents"); packageContents.Controls.Add(Pane2); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.designer.cs index d089f385e1..a4c21695a7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.designer.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.designer.cs @@ -38,7 +38,15 @@ namespace umbraco.presentation.developer.packages { /// To modify move field declaration from designer file to code-behind file. /// protected global::umbraco.uicontrols.PropertyPanel pp_name; - + + protected global::umbraco.uicontrols.PropertyPanel pp_icon; + protected global::umbraco.uicontrols.PropertyPanel pp_umbracoVersion; + protected global::System.Web.UI.WebControls.TextBox iconUrl; + protected global::System.Web.UI.WebControls.TextBox umbracoVersion; + protected global::umbraco.uicontrols.Pane Pane5; + protected global::System.Web.UI.WebControls.RegularExpressionValidator VersionValidator; + protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidator7; + /// /// packageName control. /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs index 9dd6fd85f9..fb9668488c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs @@ -16,7 +16,8 @@ namespace umbraco.presentation.developer.packages /// /// Summary description for packager. /// - public partial class Installer : UmbracoEnsuredPage + [Obsolete("This should not be used and will be removed in v8, this is kept here only for backwards compat reasons, this page should never be rendered/used")] + public class Installer : UmbracoEnsuredPage { public Installer() { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs index 780f63e469..2e95b585af 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs @@ -1,18 +1,7 @@ using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; using System.Linq; using System.Web; -using System.Web.Services.Description; -using System.Web.SessionState; -using System.Web.UI; using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using umbraco.cms.businesslogic.web; -using Umbraco.Core; -using Umbraco.Web; using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Web.UI; @@ -45,10 +34,11 @@ namespace umbraco.cms.presentation.settings.stylesheet _sheet = Services.FileService.GetStylesheetByName(Request.QueryString["id"]); if (_sheet == null) throw new InvalidOperationException("No stylesheet found with name: " + Request.QueryString["id"]); - var propName = IsPostBack ? OriginalName.Value : Request.QueryString["prop"]; + var property = HttpUtility.UrlDecode(Request.QueryString["prop"]); + var propName = IsPostBack ? OriginalName.Value : property; _stylesheetproperty = _sheet.Properties.FirstOrDefault(x => x.Name.InvariantEquals(propName)); - if (_stylesheetproperty == null) throw new InvalidOperationException("No stylesheet property found with name: " + Request.QueryString["prop"]); + if (_stylesheetproperty == null) throw new InvalidOperationException("No stylesheet property found with name: " + property); Panel1.Text = Services.TextService.Localize("stylesheet/editstylesheetproperty"); @@ -72,7 +62,7 @@ namespace umbraco.cms.presentation.settings.stylesheet var path = _sheet.Path.Replace('\\', '/'); var nodePath = string.Format(BaseTree.GetTreePathFromFilePath(path) + - ",{0}_{1}", path, _stylesheetproperty.Name); + ",{0}_{1}", path, HttpUtility.UrlEncode(_stylesheetproperty.Name)); ClientTools .SetActiveTreeType(Constants.Trees.Stylesheets) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.cs index 93643ee77b..4c564e2b95 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.cs @@ -52,7 +52,7 @@ namespace umbraco.cms.presentation.user if (!IsPostBack) { ClientTools cTools = new ClientTools(this); - cTools.SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) + cTools.SetActiveTreeType(Constants.Trees.UserPermissions) .SyncTree(Request.QueryString["id"], false); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs index d86b14eaed..47fa969fdb 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs @@ -165,16 +165,12 @@ namespace umbraco.presentation.webservices private void SortContent(string[] ids, int parentId) { - var contentService = base.ApplicationContext.Services.ContentService; - var sortedContent = new List(); + var contentService = ApplicationContext.Services.ContentService; try { - for (var i = 0; i < ids.Length; i++) - { - var id = int.Parse(ids[i]); - var c = contentService.GetById(id); - sortedContent.Add(c); - } + var intIds = ids.Select(int.Parse).ToArray(); + var allContent = contentService.GetByIds(intIds).ToDictionary(x => x.Id, x => x); + var sortedContent = intIds.Select(x => allContent[x]); // Save content with new sort order and update db+cache accordingly var sorted = contentService.Sort(sortedContent); diff --git a/src/UmbracoExamine/BaseUmbracoIndexer.cs b/src/UmbracoExamine/BaseUmbracoIndexer.cs index 0ac59c79e2..42ee690267 100644 --- a/src/UmbracoExamine/BaseUmbracoIndexer.cs +++ b/src/UmbracoExamine/BaseUmbracoIndexer.cs @@ -435,9 +435,7 @@ namespace UmbracoExamine if (e.OriginalValues.ContainsKey("icon") && e.IndexItem.ValueSet.Values.ContainsKey(IconFieldName) == false) { e.IndexItem.ValueSet.Values[IconFieldName] = new List { e.OriginalValues["icon"] }; - } - + } } - } } diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index d2f5f5a0c9..51567bf83c 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -217,8 +217,7 @@ namespace UmbracoExamine protected override void PerformIndexAll(string type) { - - const int pageSize = 1000; + const int pageSize = 10000; var pageIndex = 0; switch (type) diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index 749faaf27c..271d81ede4 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -139,8 +139,8 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True - - ..\packages\NPoco.3.3.3\lib\net45\NPoco.dll + + ..\packages\NPoco.3.3.4\lib\net45\NPoco.dll True diff --git a/src/UmbracoExamine/app.config b/src/UmbracoExamine/app.config index 069bb69de3..b47c249ea8 100644 --- a/src/UmbracoExamine/app.config +++ b/src/UmbracoExamine/app.config @@ -32,7 +32,7 @@ - + diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index daf704c839..555c11e47f 100644 --- a/src/UmbracoExamine/packages.config +++ b/src/UmbracoExamine/packages.config @@ -4,6 +4,6 @@ - + \ No newline at end of file diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 7de1435e90..1cfa73ff3f 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.IO; using System.Xml; @@ -19,6 +20,12 @@ using Template = umbraco.cms.businesslogic.template.Template; namespace umbraco.cms.businesslogic.packager { + public enum RequirementsType + { + Strict, + Legacy + } + /// /// The packager is a component which enables sharing of both data and functionality components between different umbraco installations. /// @@ -81,6 +88,10 @@ namespace umbraco.cms.businesslogic.packager public int RequirementsMinor { get; private set; } public int RequirementsPatch { get; private set; } + public RequirementsType RequirementsType { get; private set; } + + public string IconUrl { get; private set; } + /// /// The xmldocument, describing the contents of a package. /// @@ -91,16 +102,16 @@ namespace umbraco.cms.businesslogic.packager /// public Installer() { - initialize(); + Initialize(); } public Installer(int currentUserId) { - initialize(); + Initialize(); _currentUserId = currentUserId; } - private void initialize() + private void Initialize() { ContainsBinaryFileErrors = false; ContainsTemplateConflicts = false; @@ -109,40 +120,50 @@ namespace umbraco.cms.businesslogic.packager ContainsStyleSheeConflicts = false; } + [Obsolete("Use the ctor with all parameters")] + [EditorBrowsable(EditorBrowsableState.Never)] + public Installer(string name, string version, string url, string license, string licenseUrl, string author, string authorUrl, int requirementsMajor, int requirementsMinor, int requirementsPatch, string readme, string control) + { + } + /// /// Constructor /// - /// The name of the package - /// The version of the package - /// The url to a descriptionpage - /// The license under which the package is released (preferably GPL ;)) - /// The url to a licensedescription - /// The original author of the package - /// The url to the Authors website - /// Umbraco version major - /// Umbraco version minor - /// Umbraco version patch - /// The readme text - /// The name of the usercontrol used to configure the package after install - public Installer(string Name, string Version, string Url, string License, string LicenseUrl, string Author, string AuthorUrl, int RequirementsMajor, int RequirementsMinor, int RequirementsPatch, string Readme, string Control) + /// The name of the package + /// The version of the package + /// The url to a descriptionpage + /// The license under which the package is released (preferably GPL ;)) + /// The url to a licensedescription + /// The original author of the package + /// The url to the Authors website + /// Umbraco version major + /// Umbraco version minor + /// Umbraco version patch + /// The readme text + /// The name of the usercontrol used to configure the package after install + /// + /// + public Installer(string name, string version, string url, string license, string licenseUrl, string author, string authorUrl, int requirementsMajor, int requirementsMinor, int requirementsPatch, string readme, string control, RequirementsType requirementsType, string iconUrl) { ContainsBinaryFileErrors = false; ContainsTemplateConflicts = false; ContainsUnsecureFiles = false; ContainsMacroConflict = false; ContainsStyleSheeConflicts = false; - this.Name = Name; - this.Version = Version; - this.Url = Url; - this.License = License; - this.LicenseUrl = LicenseUrl; - this.RequirementsMajor = RequirementsMajor; - this.RequirementsMinor = RequirementsMinor; - this.RequirementsPatch = RequirementsPatch; - this.Author = Author; - this.AuthorUrl = AuthorUrl; - ReadMe = Readme; - this.Control = Control; + this.Name = name; + this.Version = version; + this.Url = url; + this.License = license; + this.LicenseUrl = licenseUrl; + this.RequirementsMajor = requirementsMajor; + this.RequirementsMinor = requirementsMinor; + this.RequirementsPatch = requirementsPatch; + this.RequirementsType = requirementsType; + this.Author = author; + this.AuthorUrl = authorUrl; + this.IconUrl = iconUrl; + ReadMe = readme; + this.Control = control; } #region Public Methods @@ -150,38 +171,43 @@ namespace umbraco.cms.businesslogic.packager /// /// Imports the specified package /// - /// Filename of the umbracopackage + /// Filename of the umbracopackage + /// true if the input file should be deleted after import /// - public string Import(string InputFile) + public string Import(string inputFile, bool deleteFile) { using (DisposableTimer.DebugDuration( - () => "Importing package file " + InputFile, - () => "Package file " + InputFile + "imported")) + () => "Importing package file " + inputFile, + () => "Package file " + inputFile + "imported")) { var tempDir = ""; - if (File.Exists(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + InputFile))) + if (File.Exists(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + inputFile))) { - var fi = new FileInfo(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + InputFile)); + var fi = new FileInfo(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + inputFile)); // Check if the file is a valid package if (fi.Extension.ToLower() == ".umb") { - try - { - tempDir = UnPack(fi.FullName); - LoadConfig(tempDir); - } - catch (Exception unpackE) - { - throw new Exception("Error unpacking extension...", unpackE); - } + tempDir = UnPack(fi.FullName, deleteFile); + LoadConfig(tempDir); } else throw new Exception("Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); } else - throw new Exception("Error - file not found. Could find file named '" + IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + InputFile) + "'"); + throw new Exception("Error - file not found. Could find file named '" + IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + inputFile) + "'"); return tempDir; } + + } + + /// + /// Imports the specified package + /// + /// Filename of the umbracopackage + /// + public string Import(string inputFile) + { + return Import(inputFile, true); } public int CreateManifest(string tempDir, string guid, string repoGuid) @@ -194,6 +220,7 @@ namespace umbraco.cms.businesslogic.packager var packReadme = XmlHelper.GetNodeValue(Config.DocumentElement.SelectSingleNode("/umbPackage/info/readme")); var packLicense = XmlHelper.GetNodeValue(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/license ")); var packUrl = XmlHelper.GetNodeValue(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/url ")); + var iconUrl = XmlHelper.GetNodeValue(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/iconUrl")); var enableSkins = false; var skinRepoGuid = ""; @@ -215,6 +242,7 @@ namespace umbraco.cms.businesslogic.packager insPack.Data.Readme = packReadme; insPack.Data.License = packLicense; insPack.Data.Url = packUrl; + insPack.Data.IconUrl = iconUrl; //skinning insPack.Data.EnableSkins = enableSkins; @@ -245,31 +273,22 @@ namespace umbraco.cms.businesslogic.packager foreach (XmlNode n in Config.DocumentElement.SelectNodes("//file")) { - //we enclose the whole file-moving to ensure that the entire installer doesn't crash - try - { - var destPath = GetFileName(basePath, XmlHelper.GetNodeValue(n.SelectSingleNode("orgPath"))); - var sourceFile = GetFileName(tempDir, XmlHelper.GetNodeValue(n.SelectSingleNode("guid"))); - var destFile = GetFileName(destPath, XmlHelper.GetNodeValue(n.SelectSingleNode("orgName"))); + var destPath = GetFileName(basePath, XmlHelper.GetNodeValue(n.SelectSingleNode("orgPath"))); + var sourceFile = GetFileName(tempDir, XmlHelper.GetNodeValue(n.SelectSingleNode("guid"))); + var destFile = GetFileName(destPath, XmlHelper.GetNodeValue(n.SelectSingleNode("orgName"))); - // Create the destination directory if it doesn't exist - if (Directory.Exists(destPath) == false) - Directory.CreateDirectory(destPath); - //If a file with this name exists, delete it - else if (File.Exists(destFile)) - File.Delete(destFile); + // Create the destination directory if it doesn't exist + if (Directory.Exists(destPath) == false) + Directory.CreateDirectory(destPath); + //If a file with this name exists, delete it + else if (File.Exists(destFile)) + File.Delete(destFile); - // Move the file - File.Move(sourceFile, destFile); + // Move the file + File.Move(sourceFile, destFile); - //PPH log file install - insPack.Data.Files.Add(XmlHelper.GetNodeValue(n.SelectSingleNode("orgPath")) + "/" + XmlHelper.GetNodeValue(n.SelectSingleNode("orgName"))); - - } - catch (Exception ex) - { - LogHelper.Error("Package install error", ex); - } + //PPH log file install + insPack.Data.Files.Add(XmlHelper.GetNodeValue(n.SelectSingleNode("orgPath")) + "/" + XmlHelper.GetNodeValue(n.SelectSingleNode("orgName"))); } // log that a user has install files @@ -462,9 +481,21 @@ namespace umbraco.cms.businesslogic.packager Url = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/url").FirstChild.Value; License = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/license").FirstChild.Value; LicenseUrl = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/license").Attributes.GetNamedItem("url").Value; + RequirementsMajor = int.Parse(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements/major").FirstChild.Value); RequirementsMinor = int.Parse(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements/minor").FirstChild.Value); RequirementsPatch = int.Parse(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements/patch").FirstChild.Value); + + var reqNode = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements"); + RequirementsType = reqNode != null && reqNode.Attributes != null && reqNode.Attributes["type"] != null + ? Enum.Parse(reqNode.Attributes["type"].Value, true) + : RequirementsType.Legacy; + var iconNode = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/iconUrl"); + if (iconNode != null && iconNode.FirstChild != null) + { + IconUrl = iconNode.FirstChild.Value; + } + Author = Config.DocumentElement.SelectSingleNode("/umbPackage/info/author/name").FirstChild.Value; AuthorUrl = Config.DocumentElement.SelectSingleNode("/umbPackage/info/author/website").FirstChild.Value; @@ -556,19 +587,19 @@ namespace umbraco.cms.businesslogic.packager } } - try + var readmeNode = Config.DocumentElement.SelectSingleNode("/umbPackage/info/readme"); + if (readmeNode != null) { - ReadMe = XmlHelper.GetNodeValue(Config.DocumentElement.SelectSingleNode("/umbPackage/info/readme")); + ReadMe = XmlHelper.GetNodeValue(readmeNode); } - catch { } - try + var controlNode = Config.DocumentElement.SelectSingleNode("/umbPackage/control"); + if (controlNode != null) { - Control = XmlHelper.GetNodeValue(Config.DocumentElement.SelectSingleNode("/umbPackage/control")); + Control = XmlHelper.GetNodeValue(controlNode); } - catch { } } - + /// /// This uses the old method of fetching and only supports the packages.umbraco.org repository. /// @@ -643,7 +674,8 @@ namespace umbraco.cms.businesslogic.packager return path + fileName; return path + Path.DirectorySeparatorChar + fileName; } - private static string UnPack(string zipName) + + private static string UnPack(string zipName, bool deleteFile) { // Unzip @@ -692,7 +724,12 @@ namespace umbraco.cms.businesslogic.packager // Clean up s.Close(); - File.Delete(zipName); + + if (deleteFile) + { + File.Delete(zipName); + } + return tempDir; diff --git a/src/umbraco.cms/businesslogic/Packager/Package.cs b/src/umbraco.cms/businesslogic/Packager/Package.cs index 136eb06a99..b530465dba 100644 --- a/src/umbraco.cms/businesslogic/Packager/Package.cs +++ b/src/umbraco.cms/businesslogic/Packager/Package.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Runtime.CompilerServices; using umbraco.BusinessLogic; using umbraco.DataLayer; @@ -7,6 +8,8 @@ using Umbraco.Core.Models.Membership; namespace umbraco.cms.businesslogic.packager { + [Obsolete("This class is not used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public class Package { protected static ISqlHelper SqlHelper diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index b757e60dc3..42ab59e407 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -32,8 +32,10 @@ namespace umbraco.cms.businesslogic.packager public static CreatedPackage MakeNew(string name) { - var pack = new CreatedPackage(); - pack.Data = data.MakeNew(name, IOHelper.MapPath(Settings.CreatedPackagesSettings)); + var pack = new CreatedPackage + { + Data = data.MakeNew(name, IOHelper.MapPath(Settings.CreatedPackagesSettings)) + }; return pack; diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs index ed25e1f29f..736a7c82d1 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs @@ -6,271 +6,98 @@ using System.Collections.Generic; namespace umbraco.cms.businesslogic.packager { - public class PackageInstance + public class PackageInstance { - private int _id; + public int Id { get; set; } - private string _name = ""; - private string _url = ""; - private string _folder = ""; - private string _packagePath = ""; - private string _version = ""; + public string RepositoryGuid { get; set; } - private string _author = ""; - private string _authorUrl = ""; + public string PackageGuid { get; set; } - private string _license = ""; - private string _licenseUrl = ""; + public bool HasUpdate { get; set; } - private string _readMe = ""; - - private bool _contentLoadChildNodes = false; - private string _contentNodeId = ""; + public bool EnableSkins { get; set; } - private List _macros = new List(); - private List _templates = new List(); - private List _documentTypes = new List(); - private List _stylesheets = new List(); - private List _files = new List(); - private List _languages = new List(); - private List _dictionaryItems = new List(); - private List _dataTypes = new List(); + public Guid SkinRepoGuid { get; set; } - private string _loadControl = ""; - private string _repoGuid; - private string _packageGuid; - private bool _hasUpdate; - private bool _enableSkins = false; - private Guid _skinRepoGuid = Guid.Empty; + public string Name { get; set; } - private string _actions; + public string Url { get; set; } - public int Id + public string Folder { get; set; } + + public string PackagePath { get; set; } + + public string Version { get; set; } + + /// + /// The minimum umbraco version that this package requires + /// + public Version UmbracoVersion { get; set; } + + public string Author { get; set; } + + public string AuthorUrl { get; set; } + + public string License { get; set; } + + public string LicenseUrl { get; set; } + + public string Readme { get; set; } + + public bool ContentLoadChildNodes { get; set; } + + public string ContentNodeId { get; set; } + + public List Macros { get; set; } + + public List Languages { get; set; } + + public List DictionaryItems { get; set; } + + public List Templates { get; set; } + + public List Documenttypes { get; set; } + + public List Stylesheets { get; set; } + + public List Files { get; set; } + + public string LoadControl { get; set; } + + public string Actions { get; set; } + + public List DataTypes { get; set; } + + public string IconUrl { get; set; } + + public PackageInstance() { - get { return _id; } - set {_id = value; } + SkinRepoGuid = Guid.Empty; + Name = string.Empty; + Url = string.Empty; + Folder = string.Empty; + PackagePath = string.Empty; + Version = string.Empty; + UmbracoVersion = null; + Author = string.Empty; + AuthorUrl = string.Empty; + License = string.Empty; + LicenseUrl = string.Empty; + Readme = string.Empty; + ContentNodeId = string.Empty; + IconUrl = string.Empty; + Macros = new List(); + Languages = new List(); + DictionaryItems = new List(); + Templates = new List(); + Documenttypes = new List(); + Stylesheets = new List(); + Files = new List(); + LoadControl = string.Empty; + DataTypes = new List(); + EnableSkins = false; + ContentLoadChildNodes = false; } - - public String RepositoryGuid { - get { return _repoGuid; } - set { _repoGuid = value; } - } - - public String PackageGuid { - get { return _packageGuid; } - set { _packageGuid = value; } - } - - public bool HasUpdate { - get { return _hasUpdate; } - set { _hasUpdate = value; } - } - - public bool EnableSkins - { - get { return _enableSkins; } - set { _enableSkins = value; } - } - - public Guid SkinRepoGuid - { - get { return _skinRepoGuid; } - set { _skinRepoGuid = value; } - } - - - public String Name - { - get { return _name; } - set - { - _name = value; - } - } - - public String Url - { - get { return _url; } - set - { - _url = value; - } - } - - public String Folder - { - get { return _folder; } - set - { - _folder = value; - } - } - - public String PackagePath - { - get { return _packagePath; } - set - { - _packagePath = value; - } - } - - public String Version - { - get { return _version; } - set - { - _version = value; - } - } - - public String Author - { - get { return _author; } - set - { - _author = value; - } - } - - public String AuthorUrl - { - get { return _authorUrl; } - set - { - _authorUrl = value; - } - } - - - public String License - { - get { return _license; } - set - { - _license = value; - } - } - - public String LicenseUrl - { - get { return _licenseUrl; } - set - { - _licenseUrl = value; - } - } - - public String Readme - { - get { return _readMe ; } - set - { - _readMe = value; - } - } - - public bool ContentLoadChildNodes - { - get { return _contentLoadChildNodes; } - set - { - _contentLoadChildNodes = value; - } - } - public string ContentNodeId - { - get { return _contentNodeId; } - set - { - _contentNodeId = value; - } - } - - public List Macros - { - get { return _macros; } - set - { - _macros = value; - } - } - - public List Languages { - get { return _languages; } - set { - _languages = value; - } - } - - public List DictionaryItems { - get { return _dictionaryItems; } - set { - _dictionaryItems = value; - } - } - - public List Templates - { - get { return _templates; } - set - { - _templates = value; - } - } - - public List Documenttypes - { - get { return _documentTypes; } - set - { - _documentTypes = value; - } - } - - public List Stylesheets - { - get { return _stylesheets; } - set - { - _stylesheets = value; - } - } - - public List Files - { - get { return _files; } - set - { - _files = value; - } - } - - public String LoadControl - { - get { return _loadControl; } - set - { - _loadControl = value; - } - } - - public String Actions - { - get { return _actions; } - set - { - _actions = value; - } - } - - public List DataTypes { - get { return _dataTypes; } - set { - _dataTypes = value; - } - } - - public PackageInstance() {} } } diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackagerUtility.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackagerUtility.cs index b982943e82..0ce14a4776 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackagerUtility.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackagerUtility.cs @@ -18,6 +18,7 @@ using umbraco.cms.businesslogic.template; using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic.macro; using ICSharpCode.SharpZipLib.Zip; +using Umbraco.Core; using Umbraco.Core.IO; namespace umbraco.cms.businesslogic.packager { @@ -41,17 +42,19 @@ namespace umbraco.cms.businesslogic.packager { XmlNode package = doc.CreateElement("package"); package.AppendChild(_node("name", pack.Name, doc)); package.AppendChild(_node("version", pack.Version, doc)); - + package.AppendChild(_node("iconUrl", pack.IconUrl, doc)); + XmlNode license = _node("license", pack.License, doc); license.Attributes.Append(_attribute("url", pack.LicenseUrl, doc)); package.AppendChild(license); package.AppendChild(_node("url", pack.Url, doc)); - + XmlNode Requirements = doc.CreateElement("requirements"); - Requirements.AppendChild(_node("major", "3", doc)); - Requirements.AppendChild(_node("minor", "0", doc)); - Requirements.AppendChild(_node("patch", "0", doc)); + //NOTE: The defaults are 3.0.0 - I'm just leaving that here since that's the way it's been //SD + Requirements.AppendChild(_node("major", pack.UmbracoVersion == null ? "3" : pack.UmbracoVersion.Major.ToInvariantString(), doc)); + Requirements.AppendChild(_node("minor", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Minor.ToInvariantString(), doc)); + Requirements.AppendChild(_node("patch", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Build.ToInvariantString(), doc)); package.AppendChild(Requirements); info.AppendChild(package); diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/umbraco.cms/businesslogic/Packager/data.cs index a0bb3072f6..2fef992bed 100644 --- a/src/umbraco.cms/businesslogic/Packager/data.cs +++ b/src/umbraco.cms/businesslogic/Packager/data.cs @@ -2,23 +2,28 @@ using System; using System.Xml; using System.Xml.XPath; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Xml; namespace umbraco.cms.businesslogic.packager { + /// + /// This is the xml data for installed packages. This is not the same xml as a pckage format! + /// public class data { private static XmlDocument _source; - + public static XmlDocument Source { get { - return _source; + return _source; } } @@ -40,46 +45,49 @@ namespace umbraco.cms.businesslogic.packager Directory.CreateDirectory(IOHelper.MapPath(Settings.InstalledPackagesStorage)); } - StreamWriter sw = File.CreateText(dataSource); - sw.Write(umbraco.cms.businesslogic.Packager.FileResources.PackageFiles.Packages); - sw.Flush(); - sw.Close(); + using (StreamWriter sw = File.CreateText(dataSource)) + { + sw.Write(umbraco.cms.businesslogic.Packager.FileResources.PackageFiles.Packages); + sw.Flush(); + } + } if (_source == null) { - _source = new XmlDocument(); + _source = new XmlDocument(); } - //error checking here - if (File.Exists(dataSource)) - { - var isEmpty = false; - using (var sr = new StreamReader(dataSource)) - { - if (sr.ReadToEnd().IsNullOrWhiteSpace()) - { - isEmpty = true; - } - } - if (isEmpty) - { - File.WriteAllText(dataSource, @""); - } - } + //error checking here + if (File.Exists(dataSource)) + { + var isEmpty = false; + using (var sr = new StreamReader(dataSource)) + { + if (sr.ReadToEnd().IsNullOrWhiteSpace()) + { + isEmpty = true; + } + } + if (isEmpty) + { + File.WriteAllText(dataSource, @""); + } + } _source.Load(dataSource); } public static XmlNode GetFromId(int Id, string dataSource, bool reload) { - if(reload) + if (reload) Reload(dataSource); return Source.SelectSingleNode("/packages/package [@id = '" + Id.ToString().ToUpper() + "']"); } - public static XmlNode GetFromGuid(string guid, string dataSource, bool reload) { + public static XmlNode GetFromGuid(string guid, string dataSource, bool reload) + { if (reload) Reload(dataSource); @@ -88,301 +96,318 @@ namespace umbraco.cms.businesslogic.packager public static PackageInstance MakeNew(string Name, string dataSource) { - PackageInstance retVal = new PackageInstance(); + Reload(dataSource); - try + int maxId = 1; + // Find max id + foreach (XmlNode n in Source.SelectNodes("packages/package")) { - Reload(dataSource); - - int _maxId = 1; - // Find max id - foreach (XmlNode n in Source.SelectNodes("packages/package")) - { - if (int.Parse(n.Attributes.GetNamedItem("id").Value) >= _maxId) - _maxId = int.Parse(n.Attributes.GetNamedItem("id").Value) + 1; - } - - XmlElement instance = Source.CreateElement("package"); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "id", _maxId.ToString())); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "version", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "name", Name)); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "folder", System.Guid.NewGuid().ToString())); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packagepath", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "repositoryGuid", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packageGuid", System.Guid.NewGuid().ToString())); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "hasUpdate", "false")); - - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "enableSkins", "false")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "skinRepoGuid", "")); - - XmlElement license = Source.CreateElement("license"); - license.InnerText = "MIT License"; - license.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "http://opensource.org/licenses/MIT")); - instance.AppendChild(license); - - XmlElement author = Source.CreateElement("author"); - author.InnerText = ""; - author.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); - instance.AppendChild(author); - - instance.AppendChild(XmlHelper.AddTextNode(Source, "readme", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "actions", "")); - - instance.AppendChild(XmlHelper.AddTextNode(Source, "datatypes", "")); - - XmlElement content = Source.CreateElement("content"); - content.InnerText = ""; - content.Attributes.Append(XmlHelper.AddAttribute(Source, "nodeId", "")); - content.Attributes.Append(XmlHelper.AddAttribute(Source, "loadChildNodes", "false")); - instance.AppendChild(content); - - instance.AppendChild(XmlHelper.AddTextNode(Source, "templates", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "stylesheets", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "documenttypes", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "macros", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "files", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "languages", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "dictionaryitems", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "loadcontrol", "")); - - Source.SelectSingleNode("packages").AppendChild(instance); - Source.Save(dataSource); - retVal = data.Package(_maxId, dataSource); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred", ex); + if (int.Parse(n.Attributes.GetNamedItem("id").Value) >= maxId) + maxId = int.Parse(n.Attributes.GetNamedItem("id").Value) + 1; } + XmlElement instance = Source.CreateElement("package"); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "id", maxId.ToString())); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "version", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "name", Name)); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "folder", Guid.NewGuid().ToString())); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packagepath", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "repositoryGuid", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "iconUrl", "")); + //set to current version + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "umbVersion", UmbracoVersion.Current.ToString(3))); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packageGuid", Guid.NewGuid().ToString())); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "hasUpdate", "false")); + + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "enableSkins", "false")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "skinRepoGuid", "")); + + XmlElement license = Source.CreateElement("license"); + license.InnerText = "MIT License"; + license.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "http://opensource.org/licenses/MIT")); + instance.AppendChild(license); + + XmlElement author = Source.CreateElement("author"); + author.InnerText = ""; + author.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); + instance.AppendChild(author); + + instance.AppendChild(XmlHelper.AddTextNode(Source, "readme", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "actions", "")); + + instance.AppendChild(XmlHelper.AddTextNode(Source, "datatypes", "")); + + XmlElement content = Source.CreateElement("content"); + content.InnerText = ""; + content.Attributes.Append(XmlHelper.AddAttribute(Source, "nodeId", "")); + content.Attributes.Append(XmlHelper.AddAttribute(Source, "loadChildNodes", "false")); + instance.AppendChild(content); + + instance.AppendChild(XmlHelper.AddTextNode(Source, "templates", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "stylesheets", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "documenttypes", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "macros", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "files", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "languages", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "dictionaryitems", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "loadcontrol", "")); + + Source.SelectSingleNode("packages").AppendChild(instance); + Source.Save(dataSource); + var retVal = data.Package(maxId, dataSource); + return retVal; } - public static PackageInstance Package(int id, string datasource) { - return ConvertXmlToPackage( GetFromId(id, datasource, true) ); + public static PackageInstance Package(int id, string datasource) + { + return ConvertXmlToPackage(GetFromId(id, datasource, true)); } - public static PackageInstance Package(string guid, string datasource) { - try - { - XmlNode node = GetFromGuid(guid, datasource, true); - if (node != null) - return ConvertXmlToPackage(node); - else - return new PackageInstance(); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred", ex); - return new PackageInstance(); - } + public static PackageInstance Package(string guid, string datasource) + { + XmlNode node = GetFromGuid(guid, datasource, true); + if (node != null) + return ConvertXmlToPackage(node); + else + return new PackageInstance(); } - public static List GetAllPackages(string dataSource) { + public static List GetAllPackages(string dataSource) + { Reload(dataSource); XmlNodeList nList = data.Source.SelectNodes("packages/package"); List retVal = new List(); - for (int i = 0; i < nList.Count; i++) - { - try - { - retVal.Add(ConvertXmlToPackage(nList[i])); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred in GetAllPackages", ex); - } - } - - return retVal; - } - - private static PackageInstance ConvertXmlToPackage(XmlNode n) { - PackageInstance retVal = new PackageInstance(); - - if (n != null) { - retVal.Id = int.Parse(safeAttribute("id",n)); - retVal.Name = safeAttribute("name",n); - retVal.Folder = safeAttribute("folder", n); - retVal.PackagePath = safeAttribute("packagepath", n); - retVal.Version = safeAttribute("version", n); - retVal.Url = safeAttribute("url", n); - retVal.RepositoryGuid = safeAttribute("repositoryGuid", n); - retVal.PackageGuid = safeAttribute("packageGuid", n); - retVal.HasUpdate = bool.Parse(safeAttribute("hasUpdate",n)); - - bool _enableSkins = false; - bool.TryParse(safeAttribute("enableSkins", n), out _enableSkins); - retVal.EnableSkins = _enableSkins; - - retVal.SkinRepoGuid = string.IsNullOrEmpty(safeAttribute("skinRepoGuid", n)) ? Guid.Empty : new Guid(safeAttribute("skinRepoGuid", n)); - - retVal.License = safeNodeValue(n.SelectSingleNode("license")); - retVal.LicenseUrl = n.SelectSingleNode("license").Attributes.GetNamedItem("url").Value; - - retVal.Author = safeNodeValue(n.SelectSingleNode("author")); - retVal.AuthorUrl = safeAttribute("url", n.SelectSingleNode("author")); - - retVal.Readme = safeNodeValue(n.SelectSingleNode("readme")); - retVal.Actions = safeNodeInnerXml(n.SelectSingleNode("actions")); - - retVal.ContentNodeId = safeAttribute("nodeId", n.SelectSingleNode("content")); - retVal.ContentLoadChildNodes = bool.Parse(safeAttribute("loadChildNodes",n.SelectSingleNode("content"))); - - - retVal.Macros = new List(safeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); - retVal.Macros = new List(safeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); - retVal.Templates = new List(safeNodeValue(n.SelectSingleNode("templates")).Trim(',').Split(',')); - retVal.Stylesheets = new List(safeNodeValue(n.SelectSingleNode("stylesheets")).Trim(',').Split(',')); - retVal.Documenttypes = new List(safeNodeValue(n.SelectSingleNode("documenttypes")).Trim(',').Split(',')); - retVal.Languages = new List(safeNodeValue(n.SelectSingleNode("languages")).Trim(',').Split(',')); - retVal.DictionaryItems = new List(safeNodeValue(n.SelectSingleNode("dictionaryitems")).Trim(',').Split(',')); - retVal.DataTypes = new List(safeNodeValue(n.SelectSingleNode("datatypes")).Trim(',').Split(',')); - - XmlNodeList xmlFiles = n.SelectNodes("files/file"); - retVal.Files = new List(); - - for (int i = 0; i < xmlFiles.Count; i++) - retVal.Files.Add(xmlFiles[i].InnerText); - - retVal.LoadControl = safeNodeValue(n.SelectSingleNode("loadcontrol")); + for (int i = 0; i < nList.Count; i++) + { + try + { + retVal.Add(ConvertXmlToPackage(nList[i])); } + catch (Exception ex) + { + LogHelper.Error("An error occurred in GetAllPackages", ex); + } + } return retVal; } - - public static void Delete(int Id, string dataSource) - { + + private static PackageInstance ConvertXmlToPackage(XmlNode n) + { + PackageInstance retVal = new PackageInstance(); + + if (n != null) + { + retVal.Id = int.Parse(SafeAttribute("id", n)); + retVal.Name = SafeAttribute("name", n); + retVal.Folder = SafeAttribute("folder", n); + retVal.PackagePath = SafeAttribute("packagepath", n); + retVal.Version = SafeAttribute("version", n); + retVal.Url = SafeAttribute("url", n); + retVal.RepositoryGuid = SafeAttribute("repositoryGuid", n); + retVal.PackageGuid = SafeAttribute("packageGuid", n); + retVal.HasUpdate = bool.Parse(SafeAttribute("hasUpdate", n)); + + retVal.IconUrl = SafeAttribute("iconUrl", n); + var umbVersion = SafeAttribute("umbVersion", n); + Version parsedVersion; + if (umbVersion.IsNullOrWhiteSpace() == false && Version.TryParse(umbVersion, out parsedVersion)) + { + retVal.UmbracoVersion = parsedVersion; + } + + bool enableSkins = false; + bool.TryParse(SafeAttribute("enableSkins", n), out enableSkins); + retVal.EnableSkins = enableSkins; + + retVal.SkinRepoGuid = string.IsNullOrEmpty(SafeAttribute("skinRepoGuid", n)) ? Guid.Empty : new Guid(SafeAttribute("skinRepoGuid", n)); + + retVal.License = SafeNodeValue(n.SelectSingleNode("license")); + retVal.LicenseUrl = n.SelectSingleNode("license").Attributes.GetNamedItem("url").Value; + + retVal.Author = SafeNodeValue(n.SelectSingleNode("author")); + retVal.AuthorUrl = SafeAttribute("url", n.SelectSingleNode("author")); + + retVal.Readme = SafeNodeValue(n.SelectSingleNode("readme")); + retVal.Actions = SafeNodeInnerXml(n.SelectSingleNode("actions")); + + retVal.ContentNodeId = SafeAttribute("nodeId", n.SelectSingleNode("content")); + retVal.ContentLoadChildNodes = bool.Parse(SafeAttribute("loadChildNodes", n.SelectSingleNode("content"))); + + retVal.Macros = new List(SafeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); + retVal.Macros = new List(SafeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); + retVal.Templates = new List(SafeNodeValue(n.SelectSingleNode("templates")).Trim(',').Split(',')); + retVal.Stylesheets = new List(SafeNodeValue(n.SelectSingleNode("stylesheets")).Trim(',').Split(',')); + retVal.Documenttypes = new List(SafeNodeValue(n.SelectSingleNode("documenttypes")).Trim(',').Split(',')); + retVal.Languages = new List(SafeNodeValue(n.SelectSingleNode("languages")).Trim(',').Split(',')); + retVal.DictionaryItems = new List(SafeNodeValue(n.SelectSingleNode("dictionaryitems")).Trim(',').Split(',')); + retVal.DataTypes = new List(SafeNodeValue(n.SelectSingleNode("datatypes")).Trim(',').Split(',')); + + XmlNodeList xmlFiles = n.SelectNodes("files/file"); + retVal.Files = new List(); + + for (int i = 0; i < xmlFiles.Count; i++) + retVal.Files.Add(xmlFiles[i].InnerText); + + retVal.LoadControl = SafeNodeValue(n.SelectSingleNode("loadcontrol")); + } + + return retVal; + } + + public static void Delete(int Id, string dataSource) + { Reload(dataSource); - // Remove physical xml file if any + // Remove physical xml file if any //PackageInstance p = new PackageInstance(Id); //TODO DELETE PACKAGE FOLDER... //p.Folder - XmlNode n = data.GetFromId(Id, dataSource, true); + XmlNode n = data.GetFromId(Id, dataSource, true); if (n != null) { data.Source.SelectSingleNode("/packages").RemoveChild(n); - data.Source.Save(dataSource); + data.Source.Save(dataSource); } - - } - public static void UpdateValue(XmlNode n, string Value) - { - if (n.FirstChild != null) - n.FirstChild.Value = Value; - else - { + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This method is no longer in use and will be removed in the future")] + public static void UpdateValue(XmlNode n, string Value) + { + if (n.FirstChild != null) + n.FirstChild.Value = Value; + else + { n.AppendChild(Source.CreateTextNode(Value)); - } - //Save(); - } - - public static void Save(PackageInstance package, string dataSource) - { - try - { - Reload(dataSource); - XmlNode _xmlDef = GetFromId(package.Id, dataSource, false); - _xmlDef.Attributes.GetNamedItem("name").Value = package.Name; - _xmlDef.Attributes.GetNamedItem("version").Value = package.Version; - _xmlDef.Attributes.GetNamedItem("url").Value = package.Url; - _xmlDef.Attributes.GetNamedItem("packagepath").Value = package.PackagePath; - _xmlDef.Attributes.GetNamedItem("repositoryGuid").Value = package.RepositoryGuid; - _xmlDef.Attributes.GetNamedItem("packageGuid").Value = package.PackageGuid; - - _xmlDef.Attributes.GetNamedItem("hasUpdate").Value = package.HasUpdate.ToString(); - _xmlDef.Attributes.GetNamedItem("enableSkins").Value = package.EnableSkins.ToString(); - _xmlDef.Attributes.GetNamedItem("skinRepoGuid").Value = package.SkinRepoGuid.ToString(); - - - - _xmlDef.SelectSingleNode("license").FirstChild.Value = package.License; - _xmlDef.SelectSingleNode("license").Attributes.GetNamedItem("url").Value = package.LicenseUrl; - - _xmlDef.SelectSingleNode("author").InnerText = package.Author; - _xmlDef.SelectSingleNode("author").Attributes.GetNamedItem("url").Value = package.AuthorUrl; - - _xmlDef.SelectSingleNode("readme").InnerXml = ""; - - if(_xmlDef.SelectSingleNode("actions") == null) - _xmlDef.AppendChild(XmlHelper.AddTextNode(Source, "actions", "")); - - _xmlDef.SelectSingleNode("actions").InnerXml = package.Actions; - - _xmlDef.SelectSingleNode("content").Attributes.GetNamedItem("nodeId").Value = package.ContentNodeId.ToString(); - _xmlDef.SelectSingleNode("content").Attributes.GetNamedItem("loadChildNodes").Value = package.ContentLoadChildNodes.ToString(); - - _xmlDef.SelectSingleNode("macros").InnerText = joinList(package.Macros, ','); - _xmlDef.SelectSingleNode("templates").InnerText = joinList(package.Templates, ','); - _xmlDef.SelectSingleNode("stylesheets").InnerText = joinList(package.Stylesheets, ','); - _xmlDef.SelectSingleNode("documenttypes").InnerText = joinList(package.Documenttypes, ','); - - _xmlDef.SelectSingleNode("languages").InnerText = joinList(package.Languages, ','); - _xmlDef.SelectSingleNode("dictionaryitems").InnerText = joinList(package.DictionaryItems, ','); - _xmlDef.SelectSingleNode("datatypes").InnerText = joinList(package.DataTypes, ','); - - _xmlDef.SelectSingleNode("files").InnerXml = ""; - - foreach (string fileStr in package.Files) { - if(!string.IsNullOrEmpty(fileStr.Trim())) - _xmlDef.SelectSingleNode("files").AppendChild(XmlHelper.AddTextNode(data.Source, "file", fileStr)); - } - - _xmlDef.SelectSingleNode("loadcontrol").InnerText = package.LoadControl; - - Source.Save(dataSource); - - - } - catch(Exception F) - { - LogHelper.Error("An error occurred", F); - } - - } - - private static string safeAttribute(string name, XmlNode n) { - try { - return n.Attributes.GetNamedItem(name).Value; - } catch { - return ""; } } - private static string safeNodeValue(XmlNode n) { - try { + public static void Save(PackageInstance package, string dataSource) + { + Reload(dataSource); + var xmlDef = GetFromId(package.Id, dataSource, false); + XmlHelper.SetAttribute(Source, xmlDef, "name", package.Name); + XmlHelper.SetAttribute(Source, xmlDef, "version", package.Version); + XmlHelper.SetAttribute(Source, xmlDef, "url", package.Url); + XmlHelper.SetAttribute(Source, xmlDef, "packagepath", package.PackagePath); + XmlHelper.SetAttribute(Source, xmlDef, "repositoryGuid", package.RepositoryGuid); + XmlHelper.SetAttribute(Source, xmlDef, "packageGuid", package.PackageGuid); + XmlHelper.SetAttribute(Source, xmlDef, "hasUpdate", package.HasUpdate.ToString()); + XmlHelper.SetAttribute(Source, xmlDef, "enableSkins", package.EnableSkins.ToString()); + XmlHelper.SetAttribute(Source, xmlDef, "skinRepoGuid", package.SkinRepoGuid.ToString()); + XmlHelper.SetAttribute(Source, xmlDef, "iconUrl", package.IconUrl); + XmlHelper.SetAttribute(Source, xmlDef, "umbVersion", package.UmbracoVersion.ToString(3)); + + var licenseNode = xmlDef.SelectSingleNode("license"); + if (licenseNode == null) + { + licenseNode = Source.CreateElement("license"); + xmlDef.AppendChild(licenseNode); + } + licenseNode.InnerText = package.License; + XmlHelper.SetAttribute(Source, licenseNode, "url", package.LicenseUrl); + + var authorNode = xmlDef.SelectSingleNode("author"); + if (authorNode == null) + { + authorNode = Source.CreateElement("author"); + xmlDef.AppendChild(authorNode); + } + authorNode.InnerText = package.Author; + XmlHelper.SetAttribute(Source, authorNode, "url", package.AuthorUrl); + + XmlHelper.SetCDataNode(Source, xmlDef, "readme", package.Readme); + XmlHelper.SetTextNode(Source, xmlDef, "actions", package.Actions); + + var contentNode = xmlDef.SelectSingleNode("content"); + if (contentNode == null) + { + contentNode = Source.CreateElement("content"); + xmlDef.AppendChild(contentNode); + } + XmlHelper.SetAttribute(Source, contentNode, "nodeId", package.ContentNodeId); + XmlHelper.SetAttribute(Source, contentNode, "loadChildNodes", package.ContentLoadChildNodes.ToString()); + + XmlHelper.SetTextNode(Source, xmlDef, "macros", JoinList(package.Macros, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "templates", JoinList(package.Templates, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "stylesheets", JoinList(package.Stylesheets, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "documenttypes", JoinList(package.Documenttypes, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "languages", JoinList(package.Languages, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "dictionaryitems", JoinList(package.DictionaryItems, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "datatypes", JoinList(package.DataTypes, ',')); + + var filesNode = xmlDef.SelectSingleNode("files"); + if (filesNode == null) + { + filesNode = Source.CreateElement("files"); + xmlDef.AppendChild(filesNode); + } + filesNode.InnerXml = ""; + + foreach (var fileStr in package.Files) + { + if (string.IsNullOrWhiteSpace(fileStr) == false) + filesNode.AppendChild(XmlHelper.AddTextNode(Source, "file", fileStr)); + } + + XmlHelper.SetTextNode(Source, xmlDef, "loadcontrol", package.LoadControl); + + Source.Save(dataSource); + } + + + + private static string SafeAttribute(string name, XmlNode n) + { + return n.Attributes == null || n.Attributes[name] == null ? string.Empty : n.Attributes[name].Value; + } + + private static string SafeNodeValue(XmlNode n) + { + try + { return XmlHelper.GetNodeValue(n); - } catch { - return ""; + } + catch + { + return string.Empty; } } - private static string safeNodeInnerXml(XmlNode n) { - try { + private static string SafeNodeInnerXml(XmlNode n) + { + try + { return n.InnerXml; - } catch { - return ""; + } + catch + { + return string.Empty; } } - private static string joinList(List list, char seperator) { + private static string JoinList(List list, char seperator) + { string retVal = ""; - foreach (string str in list) { - retVal += str + seperator.ToString(); + foreach (string str in list) + { + retVal += str + seperator; } return retVal.Trim(seperator); } - public data() - { + public data() + { - } + } } } diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config index 77ccbbd4c7..38e6064b0a 100644 --- a/src/umbraco.cms/packages.config +++ b/src/umbraco.cms/packages.config @@ -1,8 +1,8 @@  - - + + - + \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 36dac3f9ba..ae7176a715 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -106,13 +106,13 @@ false - - ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.0-beta4\lib\net45\ClientDependency.Core.dll True - - False - ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll + + ..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll + True False @@ -123,8 +123,8 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True - - ..\packages\NPoco.3.3.3\lib\net45\NPoco.dll + + ..\packages\NPoco.3.3.4\lib\net45\NPoco.dll True diff --git a/src/umbraco.controls/CodeArea.cs b/src/umbraco.controls/CodeArea.cs index 3d4aba7a17..9688400c07 100644 --- a/src/umbraco.controls/CodeArea.cs +++ b/src/umbraco.controls/CodeArea.cs @@ -87,6 +87,8 @@ namespace umbraco.uicontrols } + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/addon/edit/matchbrackets.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/search.js", "UmbracoRoot", ClientDependencyType.Javascript); ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/searchcursor.js", "UmbracoRoot", ClientDependencyType.Javascript); ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/dialog/dialog.js", "UmbracoRoot", ClientDependencyType.Javascript); diff --git a/src/umbraco.controls/packages.config b/src/umbraco.controls/packages.config index 1e49726191..d91fff67e2 100644 --- a/src/umbraco.controls/packages.config +++ b/src/umbraco.controls/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/umbraco.controls/umbraco.controls.csproj b/src/umbraco.controls/umbraco.controls.csproj index d58c95ce66..0c663b8b81 100644 --- a/src/umbraco.controls/umbraco.controls.csproj +++ b/src/umbraco.controls/umbraco.controls.csproj @@ -68,8 +68,8 @@ false - - ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.0-beta4\lib\net45\ClientDependency.Core.dll True