diff --git a/.gitignore b/.gitignore index a6c4639191..5b5e7660c5 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,4 @@ apidocs/api/* build/docs.zip build/ui-docs.zip build/csharp-docs.zip +build/msbuild.log diff --git a/README.md b/README.md index 53070b6917..3184d2024d 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ Umbraco CMS Umbraco is a free open source Content Management System built on the ASP.NET platform. ## Building Umbraco from source ## -The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt dev` in `src\Umbraco.Web.UI.Client`. +The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`. -If you're interested in making changes to Belle make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). Note that you can always [download a nightly build](http://nightly.umbraco.org/umbraco%207.0.0/) so you don't have to build the code yourself. +If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). + +Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself. ## Watch a introduction video ## diff --git a/appveyor.yml b/appveyor.yml index db69da4978..3044444730 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,39 +8,45 @@ build_script: FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i" - SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% - - ECHO %PATH% - - - ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER% - - - SET nuGetFolder=%CD%\..\src\packages\ + SET nuGetFolder=C:\Users\appveyor\.nuget\packages ..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL ..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet - IF EXIST ..\src\umbraco.businesslogic\packages.config ..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet ..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet - + 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 + + build.bat nopause %release% build%APPVEYOR_BUILD_NUMBER% + ECHO %PATH% test: assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll artifacts: - path: build\UmbracoCms.* + name: UmbracoFiles +- path: build\msbuild.log + name: BuildLog +deploy: +- provider: AzureBlob + storage_account_name: umbraconightlies + storage_access_key: + secure: bmEMml2SF7QLHULiePa/a01XOeIa2SxJeXuaZ+1R27b+Vb2nNUQVYiPlUyF2cZAFSHI/zO/LekRsVU1rTescGhJjF7SSjKybymI3p+F/OWpwqiu2WfFee1ofXBFx8QHw + container: umbraco-750 + artifact: UmbracoFiles + on: + branch: dev-v7 notifications: - provider: Slack auth_token: diff --git a/build/Build.bat b/build/Build.bat index 1167d9fcb6..fc0d8a69ea 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -11,20 +11,28 @@ 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. 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 @@ -32,27 +40,56 @@ 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 -%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /verbosity:minimal +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. +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. +%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "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. GOTO :EOF -:showerror -PAUSE +:error + +ECHO. +ECHO Errors were detected! + +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/InstallGit.cmd b/build/InstallGit.cmd index b6ba71df9b..e009e2594e 100644 --- a/build/InstallGit.cmd +++ b/build/InstallGit.cmd @@ -1,23 +1,33 @@ @ECHO OFF SETLOCAL -REM SETLOCAL is on, so changes to the path not persist to the actual user's path + :: SETLOCAL is on, so changes to the path not persist to the actual user's path -git.exe 2> NUL -if %ERRORLEVEL%==9009 GOTO :trydefaultpath +git.exe --version +IF %ERRORLEVEL%==9009 GOTO :trydefaultpath GOTO :EOF + :: Git is installed, no need to to anything else :trydefaultpath -path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% -git.exe 2> NUL -if %ERRORLEVEL%==9009 GOTO :showerror +PATH=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% +git.exe --version +IF %ERRORLEVEL%==9009 GOTO :showerror GOTO :EOF + :: Git is installed, no need to to anything else :showerror ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd -set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% -if %install%==y ( +SET /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% +IF %install%==y ( + :: Create a temporary batch file to execute either after elevating to admin or as-is when the user is already admin + ECHO @ECHO OFF > "%temp%\ChocoInstallGit.cmd" + ECHO SETLOCAL >> "%temp%\ChocoInstallGit.cmd" + ECHO ECHO Installing Chocolatey first >> "%temp%\ChocoInstallGit.cmd" + ECHO @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" >> "%temp%\ChocoInstallGit.cmd" + ECHO SET PATH=%%PATH%%;%%ALLUSERSPROFILE%%\chocolatey\bin >> "%temp%\ChocoInstallGit.cmd" + ECHO choco install git -y >> "%temp%\ChocoInstallGit.cmd" + GOTO :installgit -) else ( +) ELSE ( GOTO :cantcontinue ) @@ -26,7 +36,28 @@ ECHO Can't complete the build without Git being in the path. Please add it to be GOTO :EOF :installgit -ECHO Installing Chocolatey first -@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin -ECHO Installing Git through Chocolatey -choco install git \ No newline at end of file +pushd %~dp0 + :: Running prompt elevated + +:: --> Check for permissions +>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" + +:: --> If error flag set, we do not have admin. +IF '%errorlevel%' NEQ '0' ( + GOTO UACPrompt +) ELSE ( GOTO gotAdmin ) + +:UACPrompt + ECHO You're not currently running this with admin privileges, we'll now try to execute the install of Git through Chocolatey after elevating to admin privileges + ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" + ECHO UAC.ShellExecute "%temp%\ChocoInstallGit.cmd", "", "", "runas", 1 >> "%temp%\getadmin.vbs" + + "%temp%\getadmin.vbs" + EXIT /B + +:gotAdmin + IF EXIST "%temp%\getadmin.vbs" ( DEL "%temp%\getadmin.vbs" ) + pushd "%CD%" + CD /D "%~dp0" + + CALL "%temp%\ChocoInstallGit.cmd" \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index a486f8a58b..dbc7be00e4 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -27,15 +27,17 @@ - + + - - - - + + + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 66e82ac488..54d12f6844 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -16,8 +16,8 @@ umbraco - - + + @@ -32,6 +32,7 @@ + @@ -44,4 +45,4 @@ - \ No newline at end of file + diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index a77632926c..8368870186 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -21,6 +21,12 @@
+ + developer + +
+ +
views/dashboard/developer/developerdashboardvideos.html @@ -30,16 +36,21 @@
- + views/dashboard/developer/examinemanagement.html - - + + - views/dashboard/developer/xmldataintegrityreport.html + views/dashboard/developer/healthcheck.html - + + + + views/dashboard/developer/redirecturls.html + +
@@ -52,19 +63,6 @@
-
- - - - admin - - - views/dashboard/default/startupdashboardintro.html - - - -
-
@@ -80,5 +78,6 @@
+
\ No newline at end of file diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index dfb9925840..987db6c3d6 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -292,6 +292,8 @@ + + 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/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt index 580c619547..a9fbf07762 100644 --- a/build/NuSpecs/tools/trees.config.install.xdt +++ b/build/NuSpecs/tools/trees.config.install.xdt @@ -55,21 +55,29 @@ xdt:Transform="SetAttributes()" /> - - + xdt:Transform="Remove" /> + + + + + + + + - - diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 74b38086d9..df444cdd7b 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.5.0 -beta \ No newline at end of file +7.5.3 \ 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 16a91ab12a..a0a32912c9 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.5.0")] -[assembly: AssemblyInformationalVersion("7.5.0-beta")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.5.3")] +[assembly: AssemblyInformationalVersion("7.5.3")] \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index ca1f4e85a0..792b5982b1 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -9,6 +9,9 @@ namespace Umbraco.Core.Cache /// /// A cache provider that caches items in the HttpContext.Items /// + /// + /// If the Items collection is null, then this provider has no effect + /// internal class HttpRequestCacheProvider : DictionaryCacheProviderBase { // context provider @@ -34,6 +37,11 @@ namespace Umbraco.Core.Cache get { return _context != null ? _context.Items : HttpContext.Current.Items; } } + private bool HasContextItems + { + get { return (_context != null && _context.Items != null) || HttpContext.Current != null; } + } + // for unit tests public HttpRequestCacheProvider(HttpContextBase context) { @@ -50,18 +58,23 @@ namespace Umbraco.Core.Cache protected override IEnumerable GetDictionaryEntries() { const string prefix = CacheItemPrefix + "-"; + + if (HasContextItems == false) return Enumerable.Empty(); + return ContextItems.Cast() .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); } protected override void RemoveEntry(string key) { + if (HasContextItems == false) return; + ContextItems.Remove(key); } protected override object GetEntry(string key) { - return ContextItems[key]; + return HasContextItems ? ContextItems[key] : null; } #region Lock @@ -81,7 +94,9 @@ namespace Umbraco.Core.Cache get { - return new MonitorLock(ContextItems.SyncRoot); + return HasContextItems + ? (IDisposable) new MonitorLock(ContextItems.SyncRoot) + : new NoopLocker(); } } @@ -91,6 +106,9 @@ namespace Umbraco.Core.Cache public override object GetCacheItem(string cacheKey, Func getCacheItem) { + //no place to cache so just return the callback result + if (HasContextItems == false) return getCacheItem(); + cacheKey = GetCacheKey(cacheKey); Lazy result; @@ -128,5 +146,10 @@ namespace Umbraco.Core.Cache #region Insert #endregion + private class NoopLocker : DisposableObject + { + protected override void DisposeResources() + { } + } } } \ 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 8afa5639f1..feccba03b4 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -1,23 +1,23 @@ 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 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/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 1b1404a897..d743daca6a 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -291,7 +291,19 @@ namespace Umbraco.Core.Configuration.UmbracoSettings true); } } - + + [ConfigurationProperty("EnableInheritedMediaTypes")] + internal InnerTextConfigurationElement EnableInheritedMediaTypes + { + get + { + return new OptionalInnerTextConfigurationElement( + (InnerTextConfigurationElement)this["EnableInheritedMediaTypes"], + //set the default + true); + } + } + string IContentSection.NotificationEmailAddress { get { return Notifications.NotificationEmailAddress; } @@ -431,5 +443,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { get { return EnableInheritedDocumentTypes; } } + + bool IContentSection.EnableInheritedMediaTypes + { + get { return EnableInheritedMediaTypes; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index ebdd9ae637..3d5e4435b6 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -61,5 +61,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string DefaultDocumentTypeProperty { get; } bool EnableInheritedDocumentTypes { get; } + + bool EnableInheritedMediaTypes { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index 2998fc2f78..9eb6d02aa7 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -9,6 +9,8 @@ bool DisableAlternativeTemplates { get; } bool DisableFindContentByIdPath { get; } + + bool DisableRedirectUrlTracking { get; } string UrlProviderMode { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 1ed9bc034c..82f5d46b28 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -27,6 +27,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (bool) base["disableFindContentByIdPath"]; } } + [ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")] + public bool DisableRedirectUrlTracking + { + get { return (bool) base["disableRedirectUrlTracking"]; } + } + [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] public string UrlProviderMode { diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 8c2fe08dfd..72e7187960 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.5.0"); + private static readonly Version Version = new Version("7.5.3"); /// /// Gets the current version of Umbraco. @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "beta"; } } + public static string CurrentComment { get { return ""; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs index 4ff6115749..9b1de812f7 100644 --- a/src/Umbraco.Core/Constants-Examine.cs +++ b/src/Umbraco.Core/Constants-Examine.cs @@ -1,15 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { public static class Examine { + /// + /// The alias of the internal member indexer + /// + public const string InternalMemberIndexer = "InternalMemberIndexer"; + + /// + /// The alias of the internal content indexer + /// + public const string InternalIndexer = "InternalIndexer"; + + /// + /// The alias of the external content indexer + /// + public const string ExternalIndexer = "ExternalIndexer"; /// /// The alias of the internal member searcher /// @@ -19,6 +27,11 @@ namespace Umbraco.Core /// The alias of the internal content searcher /// public const string InternalSearcher = "InternalSearcher"; + + /// + /// The alias of the external content searcher + /// + public const string ExternalSearcher = "ExternalSearcher"; } } } diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index 82e3a1ff3f..4a30db9cd8 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -1,33 +1,40 @@ namespace Umbraco.Core { - public static partial class Constants - { - /// - /// Defines the identifiers for Umbraco system nodes. - /// - public static class System - { - /// - /// The integer identifier for global system root node. - /// - public const int Root = -1; + public static partial class Constants + { + /// + /// Defines the identifiers for Umbraco system nodes. + /// + public static class System + { + /// + /// The integer identifier for global system root node. + /// + public const int Root = -1; - /// - /// The integer identifier for content's recycle bin. - /// - public const int RecycleBinContent = -20; + /// + /// The integer identifier for content's recycle bin. + /// + public const int RecycleBinContent = -20; - /// - /// The integer identifier for media's recycle bin. - /// - public const int RecycleBinMedia = -21; + /// + /// The integer identifier for media's recycle bin. + /// + public const int RecycleBinMedia = -21; - public const int DefaultContentListViewDataTypeId = -95; + public const int DefaultContentListViewDataTypeId = -95; public const int DefaultMediaListViewDataTypeId = -96; public const int DefaultMembersListViewDataTypeId = -97; // identifiers for lock objects - public const int ServersLock = -331; - } - } + public const int ServersLock = -331; + } + + public static class DatabaseProviders + { + public const string SqlCe = "System.Data.SqlServerCe.4.0"; + public const string SqlServer = "System.Data.SqlClient"; + public const string MySql = "MySql.Data.MySqlClient"; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 7f8cbbf0f7..05aba6b97d 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -135,7 +135,7 @@ namespace Umbraco.Core if (string.IsNullOrEmpty(_providerName) == false) return _providerName; - _providerName = "System.Data.SqlClient"; + _providerName = Constants.DatabaseProviders.SqlServer; if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null) { if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName) == false) @@ -212,10 +212,10 @@ namespace Umbraco.Core { var provider = DbConnectionExtensions.DetectProviderFromConnectionString(connectionString); var databaseProvider = provider.ToString(); - var providerName = "System.Data.SqlClient"; + var providerName = Constants.DatabaseProviders.SqlServer; if (databaseProvider.ToLower().Contains("mysql")) { - providerName = "MySql.Data.MySqlClient"; + providerName = Constants.DatabaseProviders.MySql; } SaveConnectionString(connectionString, providerName); Initialize(string.Empty); @@ -240,10 +240,10 @@ namespace Umbraco.Core public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) { - providerName = "System.Data.SqlClient"; + providerName = Constants.DatabaseProviders.SqlServer; if (databaseProvider.ToLower().Contains("mysql")) { - providerName = "MySql.Data.MySqlClient"; + providerName = Constants.DatabaseProviders.MySql; return string.Format("Server={0}; Database={1};Uid={2};Pwd={3}", server, databaseName, user, password); } if (databaseProvider.ToLower().Contains("azure")) @@ -260,7 +260,7 @@ namespace Umbraco.Core /// Name of the database public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName) { - const string providerName = "System.Data.SqlClient"; + const string providerName = Constants.DatabaseProviders.SqlServer; var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName); SaveConnectionString(connectionString, providerName); Initialize(providerName); @@ -373,7 +373,7 @@ namespace Umbraco.Core var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName]; if (databaseSettings != null && string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) == false && string.IsNullOrWhiteSpace(databaseSettings.ProviderName) == false) { - var providerName = "System.Data.SqlClient"; + var providerName = Constants.DatabaseProviders.SqlServer; string connString = null; if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName)) { @@ -381,8 +381,7 @@ namespace Umbraco.Core connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString; } Initialize(providerName, connString); - - DetermineSqlServerVersion(); + } else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) { @@ -395,8 +394,8 @@ namespace Umbraco.Core else if (legacyConnString.ToLowerInvariant().Contains("tcp:")) { //Must be sql azure - SaveConnectionString(legacyConnString, "System.Data.SqlClient"); - Initialize("System.Data.SqlClient"); + SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer); + Initialize(Constants.DatabaseProviders.SqlServer); } else if (legacyConnString.ToLowerInvariant().Contains("datalayer=mysql")) { @@ -407,20 +406,19 @@ namespace Umbraco.Core foreach (var variable in legacyConnString.Split(';').Where(x => x.ToLowerInvariant().StartsWith("datalayer") == false)) connectionStringWithoutDatalayer = string.Format("{0}{1};", connectionStringWithoutDatalayer, variable); - SaveConnectionString(connectionStringWithoutDatalayer, "MySql.Data.MySqlClient"); - Initialize("MySql.Data.MySqlClient"); + SaveConnectionString(connectionStringWithoutDatalayer, Constants.DatabaseProviders.MySql); + Initialize(Constants.DatabaseProviders.MySql); } else { //Must be sql - SaveConnectionString(legacyConnString, "System.Data.SqlClient"); - Initialize("System.Data.SqlClient"); + SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer); + Initialize(Constants.DatabaseProviders.SqlServer); } //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); - - DetermineSqlServerVersion(); + } else { @@ -465,49 +463,6 @@ namespace Umbraco.Core Initialize(providerName); } - /// - /// Set the lazy resolution of determining the SQL server version if that is the db type we're using - /// - private void DetermineSqlServerVersion() - { - - var sqlServerSyntax = SqlSyntax as SqlServerSyntaxProvider; - if (sqlServerSyntax != null) - { - //this will not execute now, it is lazy so will only execute when we need to actually know - // the sql server version. - sqlServerSyntax.VersionName = new Lazy(() => - { - try - { - var database = this._factory.CreateDatabase(); - - var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); - var firstPart = version.Split('.')[0]; - switch (firstPart) - { - case "11": - return SqlServerVersionName.V2012; - case "10": - return SqlServerVersionName.V2008; - case "9": - return SqlServerVersionName.V2005; - case "8": - return SqlServerVersionName.V2000; - case "7": - return SqlServerVersionName.V7; - default: - return SqlServerVersionName.Other; - } - } - catch (Exception) - { - return SqlServerVersionName.Invalid; - } - }); - } - } - internal DatabaseSchemaResult ValidateDatabaseSchema() { if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) @@ -517,7 +472,7 @@ namespace Umbraco.Core { if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted - && ProviderName == "MySql.Data.MySqlClient") + && ProviderName == Constants.DatabaseProviders.MySql) { throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); } @@ -625,13 +580,8 @@ namespace Umbraco.Core var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion()); - var installedMigrationVersion = new SemVersion(0); - //we cannot check the migrations table if it doesn't exist, this will occur when upgrading to 7.3 - if (schemaResult.ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) - { - installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); - } - + var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); + var targetVersion = UmbracoVersion.Current; //In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will @@ -731,7 +681,7 @@ namespace Umbraco.Core private Attempt CheckReadyForInstall() { if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted - && ProviderName == "MySql.Data.MySqlClient") + && ProviderName == Constants.DatabaseProviders.MySql) { throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); } @@ -780,7 +730,7 @@ namespace Umbraco.Core { var dbIsSqlCe = false; if (databaseSettings != null && databaseSettings.ProviderName != null) - dbIsSqlCe = databaseSettings.ProviderName == "System.Data.SqlServerCe.4.0"; + dbIsSqlCe = databaseSettings.ProviderName == Constants.DatabaseProviders.SqlCe; var sqlCeDatabaseExists = false; if (dbIsSqlCe) { 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/Logging/ImageProcessorLogger.cs b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs new file mode 100644 index 0000000000..02ca9c2949 --- /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 = string.Format("{0} {1} : {2}", 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 = string.Format("{0} {1} : {2}", callerName, lineNumber, text); + LogHelper.Error(type, string.Empty, new ImageProcessingException(message)); + } + } +} diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs index 4f15b1e082..419389f60d 100644 --- a/src/Umbraco.Core/Models/IRedirectUrl.cs +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -4,14 +4,33 @@ 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/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 0d803c26e5..50dc7d06f8 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models.Identity Culture = Configuration.GlobalSettings.DefaultUILanguage; } - public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager) + public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager) { // NOTE the authenticationType must match the umbraco one // defined in CookieAuthenticationOptions.AuthenticationType diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 6d7cafe7c9..dff6a712c7 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -133,6 +133,13 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } } + private static void ThrowTypeException(object value, Type expected, string alias) + { + throw new InvalidOperationException(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)); + } + /// /// Gets or Sets the value of the Property /// @@ -146,55 +153,50 @@ 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; } } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index cab2861e28..0aebcb6544 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -48,7 +48,7 @@ namespace Umbraco.Core.Models { _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) - { + { } /// @@ -298,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; @@ -326,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 /// @@ -389,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) { @@ -415,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; } @@ -439,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/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 63e3104d12..c4cd28f6e0 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -16,7 +16,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")] @@ -33,9 +33,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/RedirectUrlDto.cs b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs index 6ada1dca74..ca65e4b9a1 100644 --- a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoRedirectUrl")] - [PrimaryKey("id")] + [PrimaryKey("id", autoIncrement = false)] [ExplicitColumns] class RedirectUrlDto { @@ -14,22 +14,37 @@ namespace Umbraco.Core.Models.Rdbms CreateDateUtc = DateTime.UtcNow; } - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 1, Name = "PK_umbracoRedirectUrl")] - public int Id { get; set; } + // 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("contentId")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [ForeignKey(typeof(NodeDto), Column = "id")] + [Column("id")] + [PrimaryKeyColumn(Name = "PK_umbracoRedirectUrl", AutoIncrement = false)] + public Guid 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")] + [Length(40)] + public string UrlHash { get; set; } } } diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs index 3580fcf918..bbc63d950b 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs @@ -1,11 +1,10 @@ -using Umbraco.Core.Persistence; +using System; +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 cfe3148764..bff327d286 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs @@ -1,11 +1,10 @@ -using Umbraco.Core.Persistence; +using System; +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 index 1706dd38cf..ff7746d0cf 100644 --- a/src/Umbraco.Core/Models/RedirectUrl.cs +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -5,10 +5,16 @@ 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; @@ -16,42 +22,46 @@ namespace Umbraco.Core.Models 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); - } + 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); - } + set { SetPropertyValueAndDetectChanges(value, ref _createDateUtc, Ps.Value.CreateDateUtcSelector); } } + /// public string Url { get { return _url; } - set - { - SetPropertyValueAndDetectChanges(value, ref _url, Ps.Value.UrlSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _url, Ps.Value.UrlSelector); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 5b9f63cf48..ece63b4889 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,8 +1,5 @@ using System; using System.Globalization; -using System.Linq; -using System.Threading; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -83,5 +80,19 @@ namespace Umbraco.Core.Models if (media == null) throw new ArgumentNullException("media"); return HasPathAccess(media.Path, user.StartMediaId, Constants.System.RecycleBinMedia); } + + + /// + /// Determines whether this user is an admin. + /// + /// + /// + /// true if this user is admin; otherwise, false. + /// + public static bool IsAdmin(this IUser user) + { + if (user == null) throw new ArgumentNullException("user"); + return user.UserType.Alias == "admin"; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index a8a1e80eb7..6ed424c232 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -9,15 +9,28 @@ using System.Xml; 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; @@ -25,7 +38,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. @@ -63,30 +77,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. @@ -116,9 +130,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; } @@ -201,90 +216,124 @@ namespace Umbraco.Core return Attempt.Fail(); } - private static Nullable> TryConvertToFromString(this string input, Type destinationType) + // 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)) { @@ -307,30 +356,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/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index dc3b2f3e8b..bcc1528d3f 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -107,10 +107,10 @@ namespace Umbraco.Core.Persistence var foreignSql = _syntaxProvider.Format(tableDefinition.ForeignKeys); var indexSql = _syntaxProvider.Format(tableDefinition.Indexes); - var tableExist = _db.TableExist(tableName); + var tableExist = TableExist(tableName); if (overwrite && tableExist) { - _db.DropTable(tableName); + DropTable(tableName); tableExist = false; } diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index 5145ec95c5..c0c7b2a777 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -42,13 +42,13 @@ namespace Umbraco.Core.Persistence { case DatabaseProviders.SqlServer: case DatabaseProviders.SqlAzure: - factory = DbProviderFactories.GetFactory("System.Data.SqlClient"); + factory = DbProviderFactories.GetFactory(Constants.DatabaseProviders.SqlServer); break; case DatabaseProviders.SqlServerCE: factory = DbProviderFactories.GetFactory("System.Data.SqlServerCe.4.0"); break; case DatabaseProviders.MySql: - factory = DbProviderFactories.GetFactory("MySql.Data.MySqlClient"); + factory = DbProviderFactories.GetFactory(Constants.DatabaseProviders.MySql); break; case DatabaseProviders.PostgreSQL: case DatabaseProviders.Oracle: diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 72c4275541..658b8ebabe 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -102,5 +102,14 @@ namespace Umbraco.Core.Persistence } } } + + // during tests, the thread static var can leak between tests + // this method provides a way to force-reset the variable + internal void ResetForTests() + { + if (_nonHttpInstance == null) return; + _nonHttpInstance.Dispose(); + _nonHttpInstance = null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index a69539e9ea..446bd426ad 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -99,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/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 8046de454b..9570024b09 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -266,9 +266,9 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //defaults for the member list _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "Name" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); //layouts for the list view var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; @@ -276,10 +276,10 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //defaults for the media list _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "VersionDate" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"sortOrder\",\"isSystem\":1, \"header\": \"Sort order\"},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); } private void CreateUmbracoRelationTypeData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index de9177cf8f..423c847c47 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -64,8 +64,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)}, diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 9af6d46fbb..47772c5c15 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -43,8 +43,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// public SemVersion DetermineInstalledVersionByMigrations(IMigrationEntryService migrationEntryService) { - var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); - var mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + SemVersion mostrecent = null; + + if (ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) + { + var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); + mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + } return mostrecent ?? new SemVersion(new Version(0, 0, 0)); } @@ -116,13 +121,19 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //if the error is for umbracoAccess it must be the previous version to 7.3 since that is when it is added if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoAccess")))) { - return new Version(7, 2, 5); + return new Version(7, 2, 0); } //if the error is for umbracoDeployChecksum it must be the previous version to 7.4 since that is when it is added if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoDeployChecksum")))) { - return new Version(7, 3, 4); + return new Version(7, 3, 0); + } + + //if the error is for umbracoRedirectUrl it must be the previous version to 7.5 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoRedirectUrl")))) + { + return new Version(7, 4, 0); } return UmbracoVersion.Current; diff --git a/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs new file mode 100644 index 0000000000..f6a603c70f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs @@ -0,0 +1,54 @@ +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; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations +{ + internal class LocalMigrationContext : MigrationContext + { + private readonly ISqlSyntaxProvider _sqlSyntax; + + public LocalMigrationContext(DatabaseProviders databaseProvider, Database database, ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(databaseProvider, database, logger) + { + _sqlSyntax = sqlSyntax; + } + + public IExecuteBuilder Execute + { + get { return new ExecuteBuilder(this, _sqlSyntax); } + } + + public IDeleteBuilder Delete + { + get { return new DeleteBuilder(this, _sqlSyntax); } + } + + public IAlterSyntaxBuilder Alter + { + get { return new AlterSyntaxBuilder(this, _sqlSyntax); } + } + + public ICreateBuilder Create + { + get { return new CreateBuilder(this, _sqlSyntax); } + } + + 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/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index 081865b9a1..51751b07ad 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -63,7 +63,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/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs index 56e423ebaf..508c0f284b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -14,24 +14,47 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer public override void Up() { - // don't exeucte if the table is already there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoRedirectUrl")) return; + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } - Create.Table("umbracoRedirectUrl") - .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoRedirectUrl") - .WithColumn("contentId").AsInt32().NotNullable() + private string MigrationCode(Database database) + { + var umbracoRedirectUrlTableName = "umbracoRedirectUrl"; + + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); + + var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); + + if (tables.InvariantContains(umbracoRedirectUrlTableName)) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + if (columns.Any(x => x.TableName.InvariantEquals(umbracoRedirectUrlTableName) && x.ColumnName.InvariantEquals("id") && x.DataType == "uniqueidentifier")) + return null; + localContext.Delete.Table(umbracoRedirectUrlTableName); + } + + localContext.Create.Table(umbracoRedirectUrlTableName) + .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) .WithColumn("createDateUtc").AsDateTime().NotNullable() - .WithColumn("url").AsString(2048).NotNullable(); + .WithColumn("url").AsString(2048).NotNullable() + .WithColumn("contentKey").AsGuid().NotNullable() + .WithColumn("urlHash").AsString(40).NotNullable(); - //Create.PrimaryKey("PK_umbracoRedirectUrl").OnTable("umbracoRedirectUrl").Columns(new[] { "id" }); + localContext.Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) + .OnColumn("urlHash") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("createDateUtc") + .Descending() + .WithOptions().NonClustered(); - Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") - .OnColumn("url") - .Ascending() - .OnColumn("createDateUtc") - .Ascending() - .WithOptions().NonClustered(); + localContext.Create.ForeignKey("FK_" + umbracoRedirectUrlTableName) + .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") + .ToTable("umbracoNode").PrimaryColumn("uniqueID"); + + 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..96523e25e8 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs @@ -0,0 +1,54 @@ +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(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + + private string MigrationCode(Database database) + { + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); + + //Clear all stylesheet data if the tables exist + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("cmsStylesheetProperty")) + { + localContext.Delete.FromTable("cmsStylesheetProperty").AllRows(); + localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.StylesheetProperty) }); + + localContext.Delete.Table("cmsStylesheetProperty"); + } + if (tables.InvariantContains("cmsStylesheet")) + { + localContext.Delete.FromTable("cmsStylesheet").AllRows(); + localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.Stylesheet) }); + + localContext.Delete.Table("cmsStylesheet"); + } + + return localContext.GetSql(); + } + + 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..f8e6abe42e --- /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(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + 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 (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) + { + //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", + new 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/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 88f90639d1..63278889a2 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -1,10 +1,10 @@ /* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's. * Copyright © 2011 Topten Software. All Rights Reserved. - * + * * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license - * - * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for - * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice + * + * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for + * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support */ @@ -88,7 +88,7 @@ namespace Umbraco.Core.Persistence } // Results from paged request - public class Page + public class Page { public long CurrentPage { get; set; } public long TotalPages { get; set; } @@ -164,7 +164,7 @@ namespace Umbraco.Core.Persistence connectionStringName = ConfigurationManager.ConnectionStrings[0].Name; // Work out connection string and provider name - var providerName = "System.Data.SqlClient"; + var providerName = Constants.DatabaseProviders.SqlServer; if (ConfigurationManager.ConnectionStrings[connectionStringName] != null) { if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName)) @@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence CommonConstruct(); } - enum DBType + internal enum DBType { SqlServer, SqlServerCE, @@ -202,7 +202,7 @@ namespace Umbraco.Core.Persistence if (_providerName != null) _factory = DbProviderFactories.GetFactory(_providerName); - + string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; if (dbtype.StartsWith("MySql")) _dbType = DBType.MySql; @@ -385,7 +385,7 @@ namespace Umbraco.Core.Persistence return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; } } - + // Helper to handle named parameters from object properties static Regex rxParams = new Regex(@"(? args_dest) @@ -425,8 +425,8 @@ namespace Umbraco.Core.Persistence } // Expand collections to parameter lists - if ((arg_val as System.Collections.IEnumerable) != null && - (arg_val as string) == null && + if ((arg_val as System.Collections.IEnumerable) != null && + (arg_val as string) == null && (arg_val as byte[]) == null) { var sb = new StringBuilder(); @@ -487,10 +487,10 @@ namespace Umbraco.Core.Persistence } else if (t == typeof(string)) { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. + // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. //Set before attempting to set Size, or Size will always max out at 4000 if ((item as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); + p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); p.Size = (item as string).Length + 1; if(p.Size < 4000) @@ -676,12 +676,12 @@ namespace Umbraco.Core.Persistence public bool ForceDateTimesToUtc { get; set; } // Return a typed list of pocos - public List Fetch(string sql, params object[] args) + public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(Sql sql) + public List Fetch(Sql sql) { return Fetch(sql.SQL, sql.Arguments); } @@ -726,7 +726,43 @@ namespace Umbraco.Core.Persistence return true; } - public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) + /// + /// NOTE: This is a custom mod of PetaPoco!! This builds the paging sql for different db providers + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) + { + if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle) + { + sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); + if (rxDistinct.IsMatch(sqlSelectRemoved)) + { + sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; + } + sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", + sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, skip + take }).ToArray(); + } + else if (databaseType == DBType.SqlServerCE) + { + sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, take }).ToArray(); + } + else + { + sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { take, skip }).ToArray(); + } + } + + public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) { // Add auto select clause if (EnableAutoSelect) @@ -734,38 +770,16 @@ namespace Umbraco.Core.Persistence // Split the SQL into the bits we need string sqlSelectRemoved, sqlOrderBy; - if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy)) + if (SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy) == false) throw new Exception("Unable to parse SQL statement for paged query"); if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - - // Build the SQL for the actual final result - if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle) - { - sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); - if (rxDistinct.IsMatch(sqlSelectRemoved)) - { - sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; - } - sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", - sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, skip+take }).ToArray(); - } - else if (_dbType == DBType.SqlServerCE) - { - sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, take }).ToArray(); - } - else - { - sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { take, skip }).ToArray(); - } - + + BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); } - // Fetch a page - public Page Page(long page, long itemsPerPage, string sql, params object[] args) + // Fetch a page + public Page Page(long page, long itemsPerPage, string sql, params object[] args) { string sqlCount, sqlPage; BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); @@ -791,7 +805,7 @@ namespace Umbraco.Core.Persistence return result; } - public Page Page(long page, long itemsPerPage, Sql sql) + public Page Page(long page, long itemsPerPage, Sql sql) { return Page(page, itemsPerPage, sql.SQL, sql.Arguments); } @@ -820,7 +834,7 @@ namespace Umbraco.Core.Persistence } // Return an enumerable collection of pocos - public IEnumerable Query(string sql, params object[] args) + public IEnumerable Query(string sql, params object[] args) { if (EnableAutoSelect) sql = AddSelectClause(sql); @@ -1033,7 +1047,7 @@ namespace Umbraco.Core.Persistence } private List Delegates { get; set; } private Delegate GetItem(int index) { return Delegates[index]; } - + /// /// Calls the delegate at the specified index and returns its values /// @@ -1068,7 +1082,7 @@ namespace Umbraco.Core.Persistence // Create a multi-poco factory Func CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) - { + { // Call each delegate var dels = new List(); int pos = 0; @@ -1088,7 +1102,7 @@ namespace Umbraco.Core.Persistence static Dictionary AutoMappers = new Dictionary(); static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); - // Get (or create) the multi-poco factory for a query + // Get (or create) the multi-poco factory for a query Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) { // Build a key string (this is crap, should address this at some point) @@ -1113,8 +1127,8 @@ namespace Umbraco.Core.Persistence if (MultiPocoFactories.TryGetValue(key, out oFactory)) { //mpFactory = oFactory; - return (Func)oFactory; - } + return (Func)oFactory; + } } finally { @@ -1130,9 +1144,9 @@ namespace Umbraco.Core.Persistence if (MultiPocoFactories.TryGetValue(key, out oFactory)) { return (Func)oFactory; - } - - // Create the factory + } + + // Create the factory var factory = CreateMultiPocoFactory(types, sql, r); MultiPocoFactories.Add(key, factory); @@ -1207,54 +1221,54 @@ namespace Umbraco.Core.Persistence } } - - public IEnumerable Query(Sql sql) + + public IEnumerable Query(Sql sql) { return Query(sql.SQL, sql.Arguments); } - public bool Exists(object primaryKey) + public bool Exists(object primaryKey) { return FirstOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey) != null; } - public T Single(object primaryKey) + public T Single(object primaryKey) { return Single(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); } - public T SingleOrDefault(object primaryKey) + public T SingleOrDefault(object primaryKey) { return SingleOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); } - public T Single(string sql, params object[] args) + public T Single(string sql, params object[] args) { return Query(sql, args).Single(); } - public T SingleOrDefault(string sql, params object[] args) + public T SingleOrDefault(string sql, params object[] args) { return Query(sql, args).SingleOrDefault(); } - public T First(string sql, params object[] args) + public T First(string sql, params object[] args) { return Query(sql, args).First(); } - public T FirstOrDefault(string sql, params object[] args) + public T FirstOrDefault(string sql, params object[] args) { return Query(sql, args).FirstOrDefault(); } - public T Single(Sql sql) + public T Single(Sql sql) { return Query(sql).Single(); } - public T SingleOrDefault(Sql sql) + public T SingleOrDefault(Sql sql) { return Query(sql).SingleOrDefault(); } - public T First(Sql sql) + public T First(Sql sql) { return Query(sql).First(); } - public T FirstOrDefault(Sql sql) + public T FirstOrDefault(Sql sql) { return Query(sql).FirstOrDefault(); } @@ -1285,7 +1299,7 @@ namespace Umbraco.Core.Persistence return Insert(tableName, primaryKeyName, true, poco); } - // Insert a poco into a table. If the poco has a property with the same name + // Insert a poco into a table. If the poco has a property with the same name // as the primary key the id of the new record is assigned to it. Either way, // the new id is returned. public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) @@ -1721,7 +1735,7 @@ namespace Umbraco.Core.Persistence { cmd.CommandTimeout = CommandTimeout; } - + // Call hook OnExecutingCommand(cmd); @@ -1779,8 +1793,8 @@ namespace Umbraco.Core.Persistence public class ExpandoColumn : PocoColumn { public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName]=val; } - public override object GetValue(object target) - { + public override object GetValue(object target) + { object val=null; (target as IDictionary).TryGetValue(ColumnName, out val); return val; @@ -1803,7 +1817,7 @@ namespace Umbraco.Core.Persistence } static readonly ObjectCache ObjectCache = new MemoryCache("NPoco"); - + } public class PocoData @@ -1812,7 +1826,7 @@ namespace Umbraco.Core.Persistence internal static bool UseLongKeys = false; //USE ONLY FOR TESTING - default is one hr internal static int SlidingExpirationSeconds = 3600; - + public static PocoData ForObject(object o, string primaryKeyName) { var t = o.GetType(); @@ -1836,7 +1850,7 @@ namespace Umbraco.Core.Persistence #endif return ForType(t); } - + public static PocoData ForType(Type t) { #if !PETAPOCO_NO_DYNAMIC @@ -1856,7 +1870,7 @@ namespace Umbraco.Core.Persistence InnerLock.ExitReadLock(); } - + // Cache it InnerLock.EnterWriteLock(); try @@ -1959,7 +1973,7 @@ namespace Umbraco.Core.Persistence public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r) { - //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. + //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. // This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are // returning the same structured data: // SELECT * FROM MyTable ORDER BY MyColumn @@ -1981,7 +1995,7 @@ namespace Umbraco.Core.Persistence combiner.AddInt(countColumns); key = combiner.GetCombinedHashCode(); } - + var objectCache = _managedCache.GetCache(); @@ -2029,7 +2043,7 @@ namespace Umbraco.Core.Persistence il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? if (converter != null) - il.Emit(OpCodes.Pop); // obj, obj, fieldname, + il.Emit(OpCodes.Pop); // obj, obj, fieldname, il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null if (converter != null) { @@ -2169,7 +2183,7 @@ namespace Umbraco.Core.Persistence // return it var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); - + return del; }; @@ -2178,7 +2192,7 @@ namespace Umbraco.Core.Persistence // the line belows returns existing item or adds the new value if it doesn't exist var value = (Lazy)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy { - //sliding expiration of 1 hr, if the same key isn't used in this + //sliding expiration of 1 hr, if the same key isn't used in this // timeframe it will be removed from the cache SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds) }); @@ -2272,7 +2286,7 @@ namespace Umbraco.Core.Persistence public TableInfo TableInfo { get; private set; } public Dictionary Columns { get; private set; } static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim(); - + /// /// Returns a report of the current cache being utilized by PetaPoco /// @@ -2286,7 +2300,7 @@ namespace Umbraco.Core.Persistence foreach (var pocoData in m_PocoDatas) { sb.AppendFormat("\t{0}\n", pocoData.Key); - sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); + sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); } var cache = managedCache.GetCache(); @@ -2299,7 +2313,7 @@ namespace Umbraco.Core.Persistence totalBytes = Encoding.Unicode.GetByteCount(keys); sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes); - + sb.AppendLine("\tAll Poco cache items:"); foreach (var item in cache) @@ -2315,8 +2329,8 @@ namespace Umbraco.Core.Persistence // Member variables - string _connectionString; - string _providerName; + readonly string _connectionString; + readonly string _providerName; DbProviderFactory _factory; IDbConnection _sharedConnection; IDbTransaction _transaction; @@ -2417,6 +2431,7 @@ namespace Umbraco.Core.Persistence else _rhs = sql; + _sqlFinal = null; return this; } diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 6b8e7b46ff..a72621d1a5 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -203,7 +203,8 @@ namespace Umbraco.Core.Persistence { //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; - if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008) + + if ((sqlServerSyntax != null && (int)sqlServerSyntax.GetVersionName(db) < (int)SqlServerVersionName.V2008) || SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) { //SqlCe doesn't support bulk insert statements! diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index 3cbd70803d..04f4147155 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -22,8 +22,7 @@ namespace Umbraco.Core.Persistence public static Sql From(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.From(sqlSyntax.GetQuotedTableName(tableName)); } @@ -51,11 +50,10 @@ namespace Umbraco.Core.Persistence public static Sql OrderBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnName = column.GetColumnName(); var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 var syntax = string.Format("({0}.{1})", @@ -74,13 +72,13 @@ namespace Umbraco.Core.Persistence public static Sql OrderByDescending(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnName = column.GetColumnName(); var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); - var syntax = string.Format("{0}.{1} DESC", + //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 + var syntax = string.Format("({0}.{1}) DESC", sqlSyntax.GetQuotedTableName(tableName), sqlSyntax.GetQuotedColumnName(columnName)); @@ -96,7 +94,7 @@ namespace Umbraco.Core.Persistence public static Sql GroupBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlProvider) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnName = column.GetColumnName(); return sql.GroupBy(sqlProvider.GetQuotedColumnName(columnName)); } @@ -110,8 +108,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause InnerJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.InnerJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -125,8 +122,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause LeftJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.LeftJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -140,8 +136,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause LeftOuterJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.LeftOuterJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -155,8 +150,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause RightJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.RightJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -173,13 +167,14 @@ namespace Umbraco.Core.Persistence { var leftType = typeof(TLeft); var rightType = typeof(TRight); - var leftTableName = leftType.FirstAttribute().Value; - var rightTableName = rightType.FirstAttribute().Value; + var leftTableName = leftType.GetTableName(); + var rightTableName = rightType.GetTableName(); - var left = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; - var right = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; - var leftColumnName = left.FirstAttribute().Name; - var rightColumnName = right.FirstAttribute().Name; + var leftColumn = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; + var rightColumn = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; + + var leftColumnName = leftColumn.GetColumnName(); + var rightColumnName = rightColumn.GetColumnName(); string onClause = string.Format("{0}.{1} = {2}.{3}", sqlSyntax.GetQuotedTableName(leftTableName), @@ -193,5 +188,20 @@ namespace Umbraco.Core.Persistence { return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x + " DESC").ToArray()))); } + + private static string GetTableName(this Type type) + { + // todo: returning string.Empty for now + // BUT the code bits that calls this method cannot deal with string.Empty so we + // should either throw, or fix these code bits... + var attr = type.FirstAttribute(); + return attr == null || string.IsNullOrWhiteSpace(attr.Value) ? string.Empty : attr.Value; + } + + private static string GetColumnName(this PropertyInfo column) + { + var attr = column.FirstAttribute(); + return attr == null || string.IsNullOrWhiteSpace(attr.Name) ? column.Name : attr.Name; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 4679b9b2ad..0960acc9e5 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -440,6 +440,55 @@ namespace Umbraco.Core.Persistence.Querying } return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); + + case "Replace": + string searchValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + searchValue = getter().ToString(); + } + else + { + searchValue = methodArgs[0].ToString(); + } + + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + string replaceValue; + + if (methodArgs[1].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[1], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + replaceValue = getter().ToString(); + } + else + { + replaceValue = methodArgs[1].ToString(); + } + + if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + SqlParameters.Add(RemoveQuote(searchValue)); + + SqlParameters.Add(RemoveQuote(replaceValue)); + + return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); //case "Substring": // var startIndex = Int32.Parse(args[0].ToString()) + 1; // if (args.Count == 2) @@ -501,7 +550,7 @@ namespace Umbraco.Core.Persistence.Querying //case "As": // return string.Format("{0} As {1}", r, // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - + default: throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 8d4ebecc53..9222769247 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Net.Http.Headers; using System.Text; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; @@ -63,9 +64,9 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate); + .OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; @@ -143,7 +144,7 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new List { - "DELETE FROM umbracoRedirectUrl WHERE contentId = @Id", + "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", @@ -688,6 +689,77 @@ namespace Umbraco.Core.Persistence.Repositories } } + + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + //TODO: This is what we should do , but converting to use XDocument would be breaking unless we convert + // to XmlDocument at the end of this, but again, this would be bad for memory... though still not nearly as + // bad as what is happening before! + // We'll keep using XmlDocument for now though, but XDocument xml generation is much faster: + // https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/ + // I think we already have code in here to convert XDocument to XmlDocument but in case we don't here + // it is: https://blogs.msdn.microsoft.com/marcelolr/2009/03/13/fast-way-to-convert-xmldocument-into-xdocument/ + + //// Prepare an XmlDocument with an appropriate inline DTD to match + //// the expected content + //var parent = new XElement("root", new XAttribute("id", "-1")); + //var xmlDoc = new XDocument( + // new XDocumentType("root", null, null, DocumentType.GenerateDtd()), + // parent); + + var xmlDoc = new XmlDocument(); + var doctype = xmlDoc.CreateDocumentType("root", null, null, + ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); + xmlDoc.AppendChild(doctype); + var parent = xmlDoc.CreateElement("root"); + var pIdAtt = xmlDoc.CreateAttribute("id"); + pIdAtt.Value = "-1"; + parent.Attributes.Append(pIdAtt); + xmlDoc.AppendChild(parent); + + const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode +inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type +where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) +order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; + + XmlElement last = null; + + //NOTE: Query creates a reader - does not load all into memory + foreach (var row in Database.Query(sql, new { type = new Guid(Constants.ObjectTypes.Document) })) + { + string parentId = ((int)row.parentID).ToInvariantString(); + string xml = row.xml; + int sortOrder = row.sortOrder; + + //if the parentid is changing + if (last != null && last.GetAttribute("parentID") != parentId) + { + parent = xmlDoc.GetElementById(parentId); + if (parent == null) + { + //Need to short circuit here, if the parent is not there it means that the parent is unpublished + // and therefore the child is not published either so cannot be included in the xml cache + continue; + } + } + + var xmlDocFragment = xmlDoc.CreateDocumentFragment(); + xmlDocFragment.InnerXml = xml; + + last = (XmlElement)parent.AppendChild(xmlDocFragment); + + // fix sortOrder - see notes in UpdateSortOrder + last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); + } + + return xmlDoc; + + } + public int CountPublished() { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index dc85ad3a33..d86f77168e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -251,8 +251,8 @@ AND umbracoNode.id <> @id", var nodeDto = dto.NodeDto; Database.Update(nodeDto); + // look up ContentType entry to get PrimaryKey for updating the DTO // fixme - why? we are UPDATING so we should ALREADY have a PK! - //Look up ContentType entry to get PrimaryKey for updating the DTO var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); dto.PrimaryKey = dtoPk.PrimaryKey; Database.Update(dto); @@ -262,31 +262,30 @@ AND umbracoNode.id <> @id", foreach (var composition in entity.ContentTypeComposition) Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); - //Removing a ContentType from a composition (U4-1690) - //1. Find content based on the current ContentType: entity.Id - //2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) - //3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one + // removing a ContentType from a composition (U4-1690) + // 1. Find content based on the current ContentType: entity.Id + // 2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) + // 3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one var compositionBase = entity as ContentTypeCompositionBase; if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && compositionBase.RemovedContentTypeKeyTracker.Any()) { - //Find Content based on the current ContentType + // find Content based on the current ContentType var sql = new Sql(); sql.Select("*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) .Where(x => x.ContentTypeId == entity.Id); - var contentDtos = Database.Fetch(sql); - //Loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition + + // loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition foreach (var key in compositionBase.RemovedContentTypeKeyTracker) { - //Find PropertyTypes for the removed ContentType + // find PropertyTypes for the removed ContentType var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key }); - //Loop through the Content that is based on the current ContentType in order to remove the Properties that are - //based on the PropertyTypes that belong to the removed ContentType. + // loop through the Content that is based on the current ContentType in order to remove the Properties that are + // based on the PropertyTypes that belong to the removed ContentType. foreach (var contentDto in contentDtos) { foreach (var propertyType in propertyTypes) @@ -294,51 +293,47 @@ AND umbracoNode.id <> @id", var nodeId = contentDto.NodeId; var propertyTypeId = propertyType.Id; var propertySql = new Sql().Select("cmsPropertyData.id") - .From() - .InnerJoin() - .On( - left => left.PropertyTypeId, right => right.Id) - .Where(x => x.NodeId == nodeId) - .Where(x => x.Id == propertyTypeId); + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) + .Where(x => x.NodeId == nodeId) + .Where(x => x.Id == propertyTypeId); - //Finally delete the properties that match our criteria for removing a ContentType from the composition + // finally delete the properties that match our criteria for removing a ContentType from the composition Database.Delete(new Sql("WHERE id IN (" + propertySql.SQL + ")", propertySql.Arguments)); } } } } - //Delete the allowed content type entries before adding the updated collection - Database.Delete("WHERE Id = @Id", new { Id = entity.Id }); - //Insert collection of allowed content types + // delete the allowed content type entries before re-inserting the collectino of allowed content types + Database.Delete("WHERE Id = @Id", new { entity.Id }); foreach (var allowedContentType in entity.AllowedContentTypes) { Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); + { + Id = entity.Id, + AllowedId = allowedContentType.Id.Value, + SortOrder = allowedContentType.SortOrder + }); } + // FIXME below, manage the property types - if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) + // delete ??? fixme wtf is this?! + // by excepting entries from db with entries from collections + if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) { - //Delete PropertyTypes by excepting entries from db with entries from collections var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }); var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id); var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id); var items = dbPropertyTypeAlias.Except(entityPropertyTypes); foreach (var item in items) - { - //Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted. - Database.Delete("WHERE propertyTypeId = @Id", new { Id = item }); - Database.Delete("WHERE propertytypeid = @Id", new { Id = item }); - Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", - new { Id = entity.Id, PropertyTypeId = item }); - } + DeletePropertyType(entity.Id, item); } + // delete tabs + // by excepting entries from db with entries from collections + List orphanPropertyTypeIds = null; if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) { // todo @@ -357,68 +352,97 @@ AND umbracoNode.id <> @id", // (all gone) // delete tabs that do not exist anymore - // get the tabs that are currently existing (in the db) - // get the tabs that we want, now - // and derive the tabs that we want to delete + // get the tabs that are currently existing (in the db), get the tabs that we want, + // now, and derive the tabs that we want to delete var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id }) .Select(x => x.Id) .ToList(); var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList(); - var tabsToDelete = existingPropertyGroups + var groupsToDelete = existingPropertyGroups .Except(newPropertyGroups) .ToArray(); - // move properties to generic properties, and delete the tabs - if (tabsToDelete.Length > 0) + // delete the tabs + if (groupsToDelete.Length > 0) { - Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = tabsToDelete }); - Database.Delete("WHERE id IN (@ids)", new { ids = tabsToDelete }); + // if the tab contains properties, take care of them + // - move them to 'generic properties' so they remain consistent + // - keep track of them, later on we'll figure out what to do with them + // see http://issues.umbraco.org/issue/U4-8663 + orphanPropertyTypeIds = Database.Fetch("WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }) + .Select(x => x.Id).ToList(); + Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }); + + // now we can delete the tabs + Database.Delete("WHERE id IN (@ids)", new { ids = groupsToDelete }); } } + var propertyGroupFactory = new PropertyGroupFactory(entity.Id); - //Run through all groups to insert or update entries + // insert or update groups, assign properties foreach (var propertyGroup in entity.PropertyGroups) { - var tabDto = propertyGroupFactory.BuildGroupDto(propertyGroup); - int groupPrimaryKey = propertyGroup.HasIdentity - ? Database.Update(tabDto) - : Convert.ToInt32(Database.Insert(tabDto)); + // insert or update group + var groupDto = propertyGroupFactory.BuildGroupDto(propertyGroup); + var groupId = propertyGroup.HasIdentity + ? Database.Update(groupDto) + : Convert.ToInt32(Database.Insert(groupDto)); if (propertyGroup.HasIdentity == false) - propertyGroup.Id = groupPrimaryKey; //Set Id on new PropertyGroup + propertyGroup.Id = groupId; + else + groupId = propertyGroup.Id; - //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group - //unless the PropertyGroupId has already been changed. + // assign properties to the group + // (all of them, even those that have .IsPropertyDirty("PropertyGroupId") == true, + // because it should have been set to this group anyways and better be safe) foreach (var propertyType in propertyGroup.PropertyTypes) - { - if (propertyType.IsPropertyDirty("PropertyGroupId") == false) - { - var tempGroup = propertyGroup; - propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); - } - } + propertyType.PropertyGroupId = new Lazy(() => groupId); } - //Run through all PropertyTypes to insert or update entries + // insert or update properties + // all of them, no-group and in groups foreach (var propertyType in entity.PropertyTypes) { - var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias - if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) - { - AssignDataTypeFromPropertyEditor(propertyType); - } + var groupId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //validate the alias! + // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias + if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) + AssignDataTypeFromPropertyEditor(propertyType); + + // validate the alias ValidateAlias(propertyType); - var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(tabId, propertyType); - int typePrimaryKey = propertyType.HasIdentity - ? Database.Update(propertyTypeDto) - : Convert.ToInt32(Database.Insert(propertyTypeDto)); + // insert or update property + var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(groupId, propertyType); + var typeId = propertyType.HasIdentity + ? Database.Update(propertyTypeDto) + : Convert.ToInt32(Database.Insert(propertyTypeDto)); if (propertyType.HasIdentity == false) - propertyType.Id = typePrimaryKey; //Set Id on new PropertyType + propertyType.Id = typeId; + else + typeId = propertyType.Id; + + // not an orphan anymore + if (orphanPropertyTypeIds != null) + orphanPropertyTypeIds.Remove(typeId); } + + // deal with orphan properties: those that were in a deleted tab, + // and have not been re-mapped to another tab or to 'generic properties' + if (orphanPropertyTypeIds != null) + foreach (var id in orphanPropertyTypeIds) + DeletePropertyType(entity.Id, id); + } + + private void DeletePropertyType(int contentTypeId, int propertyTypeId) + { + // first clear dependencies + Database.Delete("WHERE propertyTypeId = @Id", new { Id = propertyTypeId }); + Database.Delete("WHERE propertytypeid = @Id", new { Id = propertyTypeId }); + + // then delete the property type + Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", new { Id = contentTypeId, PropertyTypeId = propertyTypeId }); } protected IEnumerable GetAllowedContentTypeIds(int id) @@ -649,7 +673,7 @@ AND umbracoNode.id <> @id", var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); foreach (var contentType in contentTypes) - { + { var entityId = contentType.Id; var parentContentTypes = allParentContentTypes.Where(x => @@ -714,10 +738,10 @@ AND umbracoNode.id <> @id", out IDictionary> parentMediaTypeIds) { Mandate.ParameterNotNull(db, "db"); - + var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -741,7 +765,7 @@ AND umbracoNode.id <> @id", ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType) ORDER BY ctId"; - + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) }); if (result.Any() == false) @@ -848,16 +872,16 @@ AND umbracoNode.id <> @id", return mediaType; } - internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, + internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, out IDictionary> associatedTemplates, out IDictionary> parentContentTypeIds) { Mandate.ParameterNotNull(db, "db"); - + var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -890,7 +914,7 @@ AND umbracoNode.id <> @id", ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType) ORDER BY ctId"; - + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)}); if (result.Any() == false) @@ -911,7 +935,7 @@ AND umbracoNode.id <> @id", { var ct = queue.Dequeue(); - //check for default templates + //check for default templates bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); int? templateId = ct.dtTemplateId; if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index 49d52ffffa..cc13275798 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false).Where(GetBaseWhereClause(), new { id = id, NodeObjectType = NodeObjectTypeId }); - var nodeDto = Database.Fetch(sql).FirstOrDefault(); + var nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return nodeDto == null ? null : CreateEntity(nodeDto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 276f4b0f89..7c80bd10b7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -58,12 +58,12 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var macroDto = Database.Fetch(sql).FirstOrDefault(); - if (macroDto == null) + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + if (dto == null) return null; var factory = new ExternalLoginFactory(); - var entity = factory.BuildEntity(macroDto); + var entity = factory.BuildEntity(dto); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index c18765239b..e2555995d2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -11,6 +12,12 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository { + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + /// /// Get the count of published items /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs index 66c63513a4..adf0816196 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs @@ -1,17 +1,80 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IRedirectUrlRepository : IRepositoryQueryable + /// + /// Defines the repository. + /// + public interface IRedirectUrlRepository : IRepositoryQueryable { - IRedirectUrl Get(string url, int contentId); - void Delete(int id); + /// + /// 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(Guid id); + + /// + /// Deletes all redirect urls. + /// void DeleteAll(); - void DeleteContentUrls(int contentId); + + /// + /// 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); - IEnumerable GetContentUrls(int contentId); + + /// + /// 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); + + /// + /// Searches for all redirect urls that contain a given search term in their URL property. + /// + /// The term to search for. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable SearchUrls(string searchTerm, 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 54a8b4409f..8c9bec71d4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -52,9 +52,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; @@ -413,6 +413,8 @@ namespace Umbraco.Core.Persistence.Repositories { foreach (var property in entity.Properties) { + if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; + property.Id = keyDictionary[property.PropertyTypeId]; } } @@ -453,8 +455,16 @@ namespace Umbraco.Core.Persistence.Repositories Func> filterCallback = null; if (filter.IsNullOrWhiteSpace() == false) { - sbWhere.Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); + sbWhere + .Append("AND (") + .Append(SqlSyntax.GetQuotedTableName("umbracoNode")) + .Append(".") + .Append(SqlSyntax.GetQuotedColumnName("text")) + .Append(" LIKE @") + .Append(args.Count) + .Append(")"); args.Add("%" + filter + "%"); + filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 40121223a2..2c0e910428 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : _modelFactory.BuildEntity(dto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index dfbcf61b7f..0f0e797f17 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -52,9 +52,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; @@ -365,6 +365,8 @@ namespace Umbraco.Core.Persistence.Repositories { foreach (var property in ((Member)entity).Properties) { + if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; + property.Id = keyDictionary[property.PropertyTypeId]; } } @@ -622,9 +624,32 @@ namespace Umbraco.Core.Persistence.Repositories Func> filterCallback = null; if (filter.IsNullOrWhiteSpace() == false) { - sbWhere.Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ") " + - "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); - args.Add("%" + filter + "%"); + //This will build up the where clause - even though the same 'filter' is being + //applied to both columns, the parameters values passed to PetaPoco need to be + //duplicated, otherwise it gets confused :/ + var columnFilters = new List> + { + new Tuple("umbracoNode", "text"), + new Tuple("cmsMember", "LoginName") + }; + sbWhere.Append("AND ("); + for (int i = 0; i < columnFilters.Count; i++) + { + sbWhere + .Append("(") + .Append(SqlSyntax.GetQuotedTableName(columnFilters[i].Item1)) + .Append(".") + .Append(SqlSyntax.GetQuotedColumnName(columnFilters[i].Item2)) + .Append(" LIKE @") + .Append(args.Count) + .Append(") "); + args.Add(string.Format("%{0}%", filter)); + if (i < (columnFilters.Count - 1)) + { + sbWhere.Append("OR "); + } + } + sbWhere.Append(")"); filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs index 19df777666..b4a21f5d21 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.First(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 22fad9d99b..37200b2172 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -17,8 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories { public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - } + { } private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; protected override IRepositoryCachePolicyFactory CachePolicyFactory @@ -46,6 +45,8 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoAccess.id IN (@ids)", new { ids = ids }); } + sql.OrderBy(x => x.NodeId, SqlSyntax); + var factory = new PublicAccessEntryFactory(); var dtos = Database.Fetch(new AccessRulesRelator().Map, sql); return dtos.Select(factory.BuildEntity); @@ -69,7 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories .From(SqlSyntax) .LeftJoin(SqlSyntax) .On(SqlSyntax, left => left.Id, right => right.AccessId); - + return sql; } @@ -162,7 +163,5 @@ namespace Umbraco.Core.Persistence.Repositories { return entity.Key; } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index be1f03a73f..f17a0dd0d2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -49,6 +49,10 @@ namespace Umbraco.Core.Persistence.Repositories INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", FormatDeleteStatement("umbracoAccess", "nodeId"), + @"DELETE FROM umbracoRedirectUrl WHERE umbracoRedirectUrl.id IN( + SELECT TB1.id FROM umbracoRedirectUrl as TB1 + INNER JOIN umbracoNode as TB2 ON TB1.contentKey = TB2.uniqueId + WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", FormatDeleteStatement("umbracoRelation", "parentId"), FormatDeleteStatement("umbracoRelation", "childId"), FormatDeleteStatement("cmsTagRelationship", "nodeId"), diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index 4f5a756815..5452e868a0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; +using System.Text; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -10,9 +12,9 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class RedirectUrlRepository : PetaPocoRepositoryBase, IRedirectUrlRepository + internal class RedirectUrlRepository : PetaPocoRepositoryBase, IRedirectUrlRepository { - public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } @@ -21,19 +23,19 @@ namespace Umbraco.Core.Persistence.Repositories throw new NotSupportedException("This repository does not support this method."); } - protected override bool PerformExists(int id) + protected override bool PerformExists(Guid id) { return PerformGet(id) != null; } - protected override IRedirectUrl PerformGet(int id) + protected override IRedirectUrl PerformGet(Guid id) { var sql = GetBaseQuery(false).Where(x => x.Id == id); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : Map(dto); } - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable PerformGetAll(params Guid[] ids) { if (ids.Length > 2000) throw new NotSupportedException("This repository does not support more than 2000 ids."); @@ -50,7 +52,14 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*").From(SqlSyntax); + 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; } @@ -77,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories { var dto = Map(entity); Database.Insert(dto); - entity.Id = dto.Id; + entity.Id = entity.Key.GetHashCode(); } protected override void PersistUpdatedItem(IRedirectUrl entity) @@ -92,10 +101,11 @@ namespace Umbraco.Core.Persistence.Repositories return new RedirectUrlDto { - Id = redirectUrl.Id, - ContentId = redirectUrl.ContentId, + Id = redirectUrl.Key, + ContentKey = redirectUrl.ContentKey, CreateDateUtc = redirectUrl.CreateDateUtc, - Url = redirectUrl.Url + Url = redirectUrl.Url, + UrlHash = redirectUrl.Url.ToSHA1() }; } @@ -107,8 +117,10 @@ namespace Umbraco.Core.Persistence.Repositories try { url.DisableChangeTracking(); - url.Id = dto.Id; + url.Key = dto.Id; + url.Id = dto.Id.GetHashCode(); url.ContentId = dto.ContentId; + url.ContentKey = dto.ContentKey; url.CreateDateUtc = dto.CreateDateUtc; url.Url = dto.Url; return url; @@ -119,9 +131,10 @@ namespace Umbraco.Core.Persistence.Repositories } } - public IRedirectUrl Get(string url, int contentId) + public IRedirectUrl Get(string url, Guid contentKey) { - var sql = GetBaseQuery(false).Where(x => x.Url == url && x.ContentId == contentId); + var urlHash = url.ToSHA1(); + 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); } @@ -131,34 +144,40 @@ namespace Umbraco.Core.Persistence.Repositories Database.Execute("DELETE FROM umbracoRedirectUrl"); } - public void DeleteContentUrls(int contentId) + public void DeleteContentUrls(Guid contentKey) { - Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentId=@contentId", new { contentId }); + Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); } - public void Delete(int id) + public void Delete(Guid id) { Database.Delete(id); } public IRedirectUrl GetMostRecentUrl(string url) { - var dtos = Database.Fetch("SELECT * FROM umbracoRedirectUrl WHERE url=@url ORDER BY createDateUtc DESC;", - new { url }); + var urlHash = url.ToSHA1(); + var sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var dtos = Database.Fetch(sql); var dto = dtos.FirstOrDefault(); return dto == null ? null : Map(dto); } - public IEnumerable GetContentUrls(int contentId) + public IEnumerable GetContentUrls(Guid contentKey) { - var dtos = Database.Fetch("SELECT * FROM umbracoRedirectUrl WHERE contentId=@id ORDER BY createDateUtc DESC;", - new { id = contentId }); + var sql = GetBaseQuery(false) + .Where(x => x.ContentKey == contentKey) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + 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, SqlSyntax); + var sql = GetBaseQuery(false) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); var result = Database.Page(pageIndex + 1, pageSize, sql); total = Convert.ToInt32(result.TotalItems); return result.Items.Select(Map); @@ -167,8 +186,7 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) { var sql = GetBaseQuery(false) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.ContentId) - .Where("umbracoNode.path LIKE @path", new { path = "%," + rootContentId + ",%" }) + .Where(string.Format("{0}.{1} LIKE @path", SqlSyntax.GetQuotedTableName("umbracoNode"), SqlSyntax.GetQuotedColumnName("path")), new { path = "%," + rootContentId + ",%" }) .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); var result = Database.Page(pageIndex + 1, pageSize, sql); total = Convert.ToInt32(result.TotalItems); @@ -176,5 +194,17 @@ namespace Umbraco.Core.Persistence.Repositories var rules = result.Items.Select(Map); return rules; } + + public IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + var sql = GetBaseQuery(false) + .Where(string.Format("{0}.{1} LIKE @url", SqlSyntax.GetQuotedTableName("umbracoRedirectUrl"), SqlSyntax.GetQuotedColumnName("Url")), new { url = "%" + searchTerm.Trim().ToLowerInvariant() + "%" }) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + var rules = result.Items.Select(Map); + return rules; + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index 69e0307c09..be0808bb19 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.FirstOrDefault(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index 228674d829..df0ae0b224 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.FirstOrDefault(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index 8b76330dbe..68f5496101 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -26,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var tagDto = Database.Fetch(sql).FirstOrDefault(); + var tagDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (tagDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs index f30f945851..c02362f8fd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var taskDto = Database.Fetch(sql).FirstOrDefault(); + var taskDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (taskDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs index 564e949172..a970880669 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var taskDto = Database.Fetch(sql).FirstOrDefault(); + var taskDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (taskDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index e062f49235..4ef493a22a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -236,7 +236,7 @@ namespace Umbraco.Core.Persistence.Repositories private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) { Sql filteredSql; - + // Apply filter if (defaultFilter != null) { @@ -262,7 +262,7 @@ namespace Umbraco.Core.Persistence.Repositories return filteredSql; } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) + private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField, Tuple nodeIdSelect) { //copy to var so that the original isn't changed @@ -286,44 +286,71 @@ namespace Umbraco.Core.Persistence.Repositories } else { - // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through value // from most recent content version for the given order by field var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); var sortedString = string.Format("COALESCE({0},'')", "dataNvarchar"); var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); - var innerJoinTempTable = string.Format(@"INNER JOIN ( - SELECT CASE - WHEN dataInt Is Not Null THEN {0} - WHEN dataDecimal Is Not Null THEN {1} - WHEN dataDate Is Not Null THEN {2} - ELSE {3} - END AS CustomPropVal, - cd.nodeId AS CustomPropValContentId - FROM cmsDocument cd - INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.nodeId AND cpd.versionId = cd.versionId - INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId - WHERE cpt.Alias = @{4} AND cd.newest = 1) AS CustomPropData - ON CustomPropData.CustomPropValContentId = umbracoNode.id - ", sortedInt, sortedDecimal, sortedDate, sortedString, sortedSql.Arguments.Length); + //these are defaults that will be used in the query - they can be overridden for non-versioned entities or document entities + var versionQuery = " AND cpd.versionId = cd.versionId"; + var newestQuery = string.Empty; + + //cmsDocument needs to filter by the 'newest' parameter in the query + if (nodeIdSelect.Item1 == "cmsDocument") + newestQuery = " AND cd.newest = 1"; + + //members do not use versions so clear the versionQuery string + if (nodeIdSelect.Item1 == "cmsMember") + versionQuery = string.Empty; + + //needs to be an outer join since there's no guarantee that any of the nodes have values for this property + var outerJoinTempTable = string.Format(@"LEFT OUTER JOIN ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDecimal Is Not Null THEN {1} + WHEN dataDate Is Not Null THEN {2} + ELSE {3} + END AS CustomPropVal, + cd.{4} AS CustomPropValContentId + FROM {5} cd + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.{4}{6} + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE cpt.Alias = @{7}{8}) AS CustomPropData + ON CustomPropData.CustomPropValContentId = umbracoNode.id + ", sortedInt, sortedDecimal, sortedDate, sortedString, nodeIdSelect.Item2, nodeIdSelect.Item1, versionQuery, sortedSql.Arguments.Length, newestQuery); + + //insert this just above the first LEFT OUTER JOIN (for cmsDocument) or the last WHERE (everything else) + string newSql; + if (nodeIdSelect.Item1 == "cmsDocument") + newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), outerJoinTempTable); + else + newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable); - //insert this just above the LEFT OUTER JOIN - var newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); var newArgs = sortedSql.Arguments.ToList(); newArgs.Add(orderBy); sortedSql = new Sql(newSql, newArgs.ToArray()); - sortedSql.OrderBy("CustomPropData.CustomPropVal"); if (orderDirection == Direction.Descending) { - sortedSql.Append(" DESC"); - } + sortedSql.OrderByDescending("CustomPropData.CustomPropVal"); + } + else + { + sortedSql.OrderBy("CustomPropData.CustomPropVal"); + } } + //no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique. + // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column + // is empty for many nodes) + // see: http://issues.umbraco.org/issue/U4-8831 + sortedSql.OrderBy("umbracoNode.id"); + return sortedSql; - + } /// @@ -371,7 +398,7 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy, orderBySystemField); + orderDirection, orderBy, orderBySystemField, nodeIdSelect); // Get page of results and total count IEnumerable result; @@ -409,7 +436,7 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var fullQuery = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy, orderBySystemField); + orderDirection, orderBy, orderBySystemField, nodeIdSelect); return processQuery(fullQuery); } else @@ -483,17 +510,6 @@ WHERE EXISTS( var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); - - foreach (var property in newProperties) - { - var propertyDataDto = new PropertyDataDto { NodeId = def.Id, PropertyTypeId = property.PropertyTypeId, VersionId = def.Version }; - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - - property.Version = def.Version; - property.Id = primaryKey; - } - foreach (var property in properties) { //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index e61b23c8ec..366376b953 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; @@ -66,6 +67,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string Format(ForeignKeyDefinition foreignKey); string FormatColumnRename(string tableName, string oldName, string newName); string FormatTableRename(string oldName, string newName); + Sql SelectTop(Sql sql, int top); bool SupportsClustered(); bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index a6d1d690ba..449f5fb3b1 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -29,7 +29,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax public override string GetQuotedTableName(string tableName) { - return string.Format("[{0}]", tableName); + if (tableName.Contains(".") == false) + return string.Format("[{0}]", tableName); + + var tableNameParts = tableName.Split(new[] { '.' }, 2); + return string.Format("[{0}].[{1}]", tableNameParts[0], tableNameParts[1]); } public override string GetQuotedColumnName(string columnName) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 7167a5af95..ceb8e9c83f 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for MySql /// - [SqlSyntaxProviderAttribute("MySql.Data.MySqlClient")] + [SqlSyntaxProvider(Constants.DatabaseProviders.MySql)] public class MySqlSyntaxProvider : SqlSyntaxProviderBase { private readonly ILogger _logger; @@ -178,6 +178,11 @@ ORDER BY TABLE_NAME, INDEX_NAME", return result > 0; } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(string.Concat(sql.SQL, " LIMIT ", top), sql.Arguments); + } + public override bool SupportsClustered() { return true; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 23e0a1bb87..69ebe11a14 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for Sql Ce /// - [SqlSyntaxProviderAttribute("System.Data.SqlServerCe.4.0")] + [SqlSyntaxProviderAttribute(Constants.DatabaseProviders.SqlCe)] public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public SqlCeSyntaxProvider() @@ -18,6 +18,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + public override bool SupportsClustered() { return false; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 9cb0ce327d..4c35c90e1b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -8,18 +8,63 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for Sql Server /// - [SqlSyntaxProviderAttribute("System.Data.SqlClient")] + [SqlSyntaxProviderAttribute(Constants.DatabaseProviders.SqlServer)] public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public SqlServerSyntaxProvider() { - } + } /// /// Gets/sets the version of the current SQL server instance /// - internal Lazy VersionName { get; set; } + internal SqlServerVersionName GetVersionName(Database database) + { + if (_versionName.HasValue) + return _versionName.Value; + + try + { + var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); + var firstPart = version.Split('.')[0]; + switch (firstPart) + { + case "13": + _versionName = SqlServerVersionName.V2014; + break; + case "12": + _versionName = SqlServerVersionName.V2014; + break; + case "11": + _versionName = SqlServerVersionName.V2012; + break; + case "10": + _versionName = SqlServerVersionName.V2008; + break; + case "9": + _versionName = SqlServerVersionName.V2005; + break; + case "8": + _versionName = SqlServerVersionName.V2000; + break; + case "7": + _versionName = SqlServerVersionName.V7; + break; + default: + _versionName = SqlServerVersionName.Other; + break; + } + } + catch (Exception) + { + _versionName = SqlServerVersionName.Invalid; + } + + return _versionName.Value; + } + + private SqlServerVersionName? _versionName; /// /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only @@ -104,6 +149,11 @@ order by T.name, I.name"); return column.IsIdentity ? GetIdentityString(column) : string.Empty; } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + private static string GetIdentityString(ColumnDefinition column) { return "IDENTITY(1,1)"; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs index 37870a9536..2f50f39435 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs @@ -3,6 +3,9 @@ /// /// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...) /// + /// + /// see: https://support.microsoft.com/en-us/kb/321185 + /// internal enum SqlServerVersionName { Invalid = -1, @@ -11,6 +14,8 @@ V2005 = 2, V2008 = 3, V2012 = 4, - Other = 5 + V2014 = 5, + V2016 = 6, + Other = 100 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 50417761aa..a2baa8132d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -503,6 +503,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax protected abstract string FormatIdentity(ColumnDefinition column); + public abstract Sql SelectTop(Sql sql, int top); + public virtual string DeleteDefaultConstraint { get diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index af18d9c3d9..1231765f20 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -22,12 +22,23 @@ /// /// 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 a0490348f3..5ca30f2860 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using StackExchange.Profiling; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence { @@ -154,5 +155,41 @@ namespace Umbraco.Core.Persistence } base.OnExecutedCommand(cmd); } + + /// + /// We are overriding this in the case that we are using SQL Server 2012+ so we can make paging more efficient than the default PetaPoco paging + /// see: http://issues.umbraco.org/issue/U4-8837 + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal override void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) + { + if (databaseType == DBType.SqlServer) + { + //we need to check it's version to see what kind of paging format we can use + //TODO: This is a hack, but we don't have access to the SqlSyntaxProvider here, we can in v8 but not now otherwise + // this would be a breaking change. + var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; + if (sqlServerSyntax != null) + { + if ((int) sqlServerSyntax.GetVersionName(this) >= (int) SqlServerVersionName.V2012) + { + //we can use the good paging! to do that we are going to change the databaseType to SQLCE since + //it also uses the good paging syntax. + base.BuildSqlDbSpecificPagingQuery(DBType.SqlServerCE, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); + return; + } + } + } + + //use the defaults + base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 39bcf85b12..a3b12a6688 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // // - var sourceString = source.ToString(); + var sourceString = source != null ? source.ToString() : null; if (string.IsNullOrWhiteSpace(sourceString)) return Enumerable.Empty(); //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string diff --git a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs index 45492423ab..94df1b88f1 100644 --- a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs +++ b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs @@ -17,8 +17,15 @@ namespace Umbraco.Core.Publishing _contentService = contentService; } - public void CheckPendingAndProcess() + /// + /// Processes scheduled operations + /// + /// + /// Returns the number of items successfully completed + /// + public int CheckPendingAndProcess() { + var counter = 0; foreach (var d in _contentService.GetContentForRelease()) { try @@ -32,10 +39,14 @@ namespace Umbraco.Core.Publishing LogHelper.Error("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception); } else - { + { LogHelper.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType); } } + else + { + counter++; + } } catch (Exception ee) { @@ -48,7 +59,11 @@ namespace Umbraco.Core.Publishing try { d.ExpireDate = null; - _contentService.UnPublish(d, (int)d.GetWriterProfile().Id); + var result = _contentService.UnPublish(d, (int)d.GetWriterProfile().Id); + if (result) + { + counter++; + } } catch (Exception ee) { @@ -56,6 +71,8 @@ namespace Umbraco.Core.Publishing throw; } } + + return counter; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs new file mode 100644 index 0000000000..819fa87a56 --- /dev/null +++ b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -0,0 +1,31 @@ +using System.Configuration; +using System.DirectoryServices.AccountManagement; +using System.Threading.Tasks; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker + { + public virtual string ActiveDirectoryDomain { + get { + return ConfigurationManager.AppSettings["ActiveDirectoryDomain"]; + } + } + + public Task CheckPasswordAsync(BackOfficeIdentityUser user, string password) + { + bool isValid; + using (var pc = new PrincipalContext(ContextType.Domain, ActiveDirectoryDomain)) + { + isValid = pc.ValidateCredentials(user.UserName, password); + } + + var result = isValid + ? BackOfficeUserPasswordCheckerResult.ValidCredentials + : BackOfficeUserPasswordCheckerResult.InvalidCredentials; + + return Task.FromResult(result); + } + } +} diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 524663c9c5..9007c45946 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -307,11 +307,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/BackOfficeClaimsIdentityFactory.cs b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs index 9c46ae69f4..7c2373a000 100644 --- a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs +++ b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs @@ -7,7 +7,8 @@ using Umbraco.Core.Models.Identity; namespace Umbraco.Core.Security { - public class BackOfficeClaimsIdentityFactory : ClaimsIdentityFactory + public class BackOfficeClaimsIdentityFactory : ClaimsIdentityFactory + where T: BackOfficeIdentityUser { public BackOfficeClaimsIdentityFactory() { @@ -20,7 +21,7 @@ namespace Umbraco.Core.Security /// /// /// - public override async Task CreateAsync(UserManager manager, BackOfficeIdentityUser user, string authenticationType) + public override async Task CreateAsync(UserManager manager, T user, string authenticationType) { var baseIdentity = await base.CreateAsync(manager, user, authenticationType); @@ -42,4 +43,9 @@ namespace Umbraco.Core.Security return umbracoIdentity; } } + + public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory + { + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index f1d18b9d0f..7c65b43291 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Security private readonly ILogger _logger; private readonly IOwinRequest _request; - public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) + public BackOfficeSignInManager(UserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) : base(userManager, authenticationManager) { if (logger == null) throw new ArgumentNullException("logger"); @@ -29,13 +29,13 @@ namespace Umbraco.Core.Security public override Task CreateUserIdentityAsync(BackOfficeIdentityUser user) { - return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); + return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); } public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, ILogger logger) { return new BackOfficeSignInManager( - context.GetUserManager(), + context.GetBackOfficeUserManager(), context.Authentication, logger, context.Request); @@ -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 e48079c10b..2b68fd07eb 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Web.Security; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin.Security.DataProtection; using Umbraco.Core.Models.Identity; using Umbraco.Core.Services; @@ -15,6 +16,8 @@ namespace Umbraco.Core.Security /// public class BackOfficeUserManager : BackOfficeUserManager { + public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; + public BackOfficeUserManager(IUserStore store) : base(store) { @@ -26,9 +29,8 @@ namespace Umbraco.Core.Security MembershipProviderBase membershipProvider) : base(store) { - if (options == null) throw new ArgumentNullException("options"); - var manager = new BackOfficeUserManager(store); - InitUserManager(manager, membershipProvider, options); + if (options == null) throw new ArgumentNullException("options");; + InitUserManager(this, membershipProvider, options); } #region Static Create methods @@ -69,7 +71,7 @@ namespace Umbraco.Core.Security { var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider); return manager; - } + } #endregion /// @@ -79,65 +81,15 @@ namespace Umbraco.Core.Security /// /// /// - protected void InitUserManager(BackOfficeUserManager manager, MembershipProviderBase membershipProvider, IdentityFactoryOptions options) + protected void InitUserManager( + BackOfficeUserManager manager, + MembershipProviderBase membershipProvider, + IdentityFactoryOptions options) { - // Configure validation logic for usernames - manager.UserValidator = new UserValidator(manager) - { - AllowOnlyAlphanumericUserNames = false, - RequireUniqueEmail = true - }; - - // Configure validation logic for passwords - manager.PasswordValidator = new PasswordValidator - { - RequiredLength = membershipProvider.MinRequiredPasswordLength, - RequireNonLetterOrDigit = membershipProvider.MinRequiredNonAlphanumericCharacters > 0, - RequireDigit = false, - RequireLowercase = false, - RequireUppercase = false - //TODO: Do we support the old regex match thing that membership providers used? - }; - - //use a custom hasher based on our membership provider - manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider); - - var dataProtectionProvider = options.DataProtectionProvider; - if (dataProtectionProvider != null) - { - manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); - } - - manager.UserLockoutEnabledByDefault = true; - manager.MaxFailedAccessAttemptsBeforeLockout = membershipProvider.MaxInvalidPasswordAttempts; - //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked - // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are - // locked out or not. - manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(30); - - //custom identity factory for creating the identity object for which we auth against in the back office - manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); - - 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 - //// You can write your own provider and plug in here. - //manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider - //{ - // MessageFormat = "Your security code is: {0}" - //}); - //manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider - //{ - // Subject = "Security Code", - // BodyFormat = "Your security code is: {0}" - //}); - - //manager.SmsService = new SmsService(); + //NOTE: This method is mostly here for backwards compat + base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider); } - } /// @@ -180,6 +132,73 @@ namespace Umbraco.Core.Security } #endregion + /// + /// Initializes the user manager with the correct options + /// + /// + /// + /// + /// + protected void InitUserManager( + BackOfficeUserManager manager, + MembershipProviderBase membershipProvider, + IDataProtectionProvider dataProtectionProvider) + { + // Configure validation logic for usernames + manager.UserValidator = new UserValidator(manager) + { + AllowOnlyAlphanumericUserNames = false, + RequireUniqueEmail = true + }; + + // Configure validation logic for passwords + manager.PasswordValidator = new PasswordValidator + { + RequiredLength = membershipProvider.MinRequiredPasswordLength, + RequireNonLetterOrDigit = membershipProvider.MinRequiredNonAlphanumericCharacters > 0, + RequireDigit = false, + RequireLowercase = false, + RequireUppercase = false + //TODO: Do we support the old regex match thing that membership providers used? + }; + + //use a custom hasher based on our membership provider + manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider); + + if (dataProtectionProvider != null) + { + manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); + } + + manager.UserLockoutEnabledByDefault = true; + manager.MaxFailedAccessAttemptsBeforeLockout = membershipProvider.MaxInvalidPasswordAttempts; + //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked + // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are + // locked out or not. + manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(30); + + //custom identity factory for creating the identity object for which we auth against in the back office + manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); + + 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 + //// You can write your own provider and plug in here. + //manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider + //{ + // MessageFormat = "Your security code is: {0}" + //}); + //manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider + //{ + // Subject = "Security Code", + // BodyFormat = "Your security code is: {0}" + //}); + + //manager.SmsService = new SmsService(); + } + /// /// Logic used to validate a username and password /// @@ -200,7 +219,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/BackOfficeUserManagerMarker.cs b/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs new file mode 100644 index 0000000000..9dd5afd9da --- /dev/null +++ b/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + /// + /// This class is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked + /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this + /// class to resolve the 'real' type of the registered user manager + /// + /// + /// + internal class BackOfficeUserManagerMarker : IBackOfficeUserManagerMarker + where TManager : BackOfficeUserManager + where TUser : BackOfficeIdentityUser + { + public BackOfficeUserManager GetManager(IOwinContext owin) + { + var mgr = owin.Get() as BackOfficeUserManager; + if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager)); + return mgr; + } + } +} \ No newline at end of file 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(" + /// This interface is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked + /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this + /// class to resolve the 'real' type of the registered user manager + /// + internal interface IBackOfficeUserManagerMarker + { + BackOfficeUserManager GetManager(IOwinContext owin); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/OwinExtensions.cs b/src/Umbraco.Core/Security/OwinExtensions.cs new file mode 100644 index 0000000000..251f008a8c --- /dev/null +++ b/src/Umbraco.Core/Security/OwinExtensions.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + public static class OwinExtensions + { + /// + /// Gets the back office sign in manager out of OWIN + /// + /// + /// + public static BackOfficeSignInManager GetBackOfficeSignInManager(this IOwinContext owinContext) + { + var mgr = owinContext.Get(); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext)); + } + return mgr; + } + + /// + /// Gets the back office user manager out of OWIN + /// + /// + /// + /// + /// This is required because to extract the user manager we need to user a custom service since owin only deals in generics and + /// developers could register their own user manager types + /// + public static BackOfficeUserManager GetBackOfficeUserManager(this IOwinContext owinContext) + { + var marker = owinContext.Get(BackOfficeUserManager.OwinMarkerKey); + if (marker == null) throw new NullReferenceException("No " + typeof(IBackOfficeUserManagerMarker) + " has been registered with Owin which means that no Umbraco back office user manager has been registered"); + + var mgr = marker.GetManager(owinContext); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeUserManager)); + } + return mgr; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 7fcb915b30..cbcd5ecc05 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Globalization; using System.Linq; using System.Threading; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; @@ -636,7 +637,7 @@ namespace Umbraco.Core.Services query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - + return contents; } } @@ -1714,6 +1715,20 @@ namespace Umbraco.Core.Services return true; } + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + var result = repository.BuildXmlCache(); + return result; + } + } + /// /// Rebuilds all xml content in the cmsContentXml table for all documents /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 34dcdc01a9..1ea43c0f08 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -803,10 +803,13 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) { + var deletedContentTypes = new List() {contentType}; + deletedContentTypes.AddRange(contentType.Descendants().OfType()); + repository.Delete(contentType); uow.Commit(); - DeletedContentType.RaiseEvent(new DeleteEventArgs(contentType, false), this); + DeletedContentType.RaiseEvent(new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete ContentType performed by user"), userId, contentType.Id); @@ -838,14 +841,18 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) { + var deletedContentTypes = new List(); + deletedContentTypes.AddRange(asArray); + foreach (var contentType in asArray) { + deletedContentTypes.AddRange(contentType.Descendants().OfType()); repository.Delete(contentType); } uow.Commit(); - DeletedContentType.RaiseEvent(new DeleteEventArgs(asArray, false), this); + DeletedContentType.RaiseEvent(new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1); @@ -1238,11 +1245,13 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) { + var deletedMediaTypes = new List() {mediaType}; + deletedMediaTypes.AddRange(mediaType.Descendants().OfType()); repository.Delete(mediaType); uow.Commit(); - DeletedMediaType.RaiseEvent(new DeleteEventArgs(mediaType, false), this); + DeletedMediaType.RaiseEvent(new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete MediaType performed by user"), userId, mediaType.Id); @@ -1271,13 +1280,17 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) { + var deletedMediaTypes = new List(); + deletedMediaTypes.AddRange(asArray); + foreach (var mediaType in asArray) { + deletedMediaTypes.AddRange(mediaType.Descendants().OfType()); repository.Delete(mediaType); } uow.Commit(); - DeletedMediaType.RaiseEvent(new DeleteEventArgs(asArray, false), this); + DeletedMediaType.RaiseEvent(new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete MediaTypes performed by user"), userId, -1); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 5e90559233..fa4130f8c4 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Xml; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -94,6 +95,12 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + /// /// Rebuilds all xml content in the cmsContentXml table for all documents /// diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index c067b2d615..ccd0821b8a 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -29,6 +29,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 index 554b4ed0fd..1249b3b664 100644 --- a/src/Umbraco.Core/Services/IRedirectUrlService.cs +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -1,26 +1,86 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services { + /// + /// + /// public interface IRedirectUrlService : IService { - void Register(string url, int contentId); + /// + /// 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); - void DeleteContentRedirectUrls(int contentId); + /// + /// 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); - void Delete(int id); + /// + /// Deletes a redirect url. + /// + /// The redirect url identifier. + void Delete(Guid 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); - IEnumerable GetContentRedirectUrls(int contentId); + /// + /// 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); + + /// + /// Searches for all redirect urls that contain a given search term in their URL property. + /// + /// The term to search for. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total); } } diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 41e2ff698e..5d51e20008 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -56,37 +56,88 @@ 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; + + var content = (IContent) entity; + + // lazily get versions - into a list to ensure we can enumerate multiple times + List allVersions = null; int 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(string.Format("Notification type: {0} sent to {1} ({2})", + action, 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>(); + + int 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(string.Format("Notification type: {0} sent to {1} ({2})", + action, u.Name, u.Email)); } catch (Exception ex) { @@ -96,6 +147,7 @@ namespace Umbraco.Core.Services } } + /// /// Gets the notifications for the user /// @@ -119,10 +171,20 @@ namespace Umbraco.Core.Services /// public IEnumerable GetUserNotifications(IUser user, string path) { - var userNotifications = GetUserNotifications(user).ToArray(); - var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - var result = userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); - return result; + 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); + return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); } /// diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs index 6984942ccd..0283332401 100644 --- a/src/Umbraco.Core/Services/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -14,16 +14,16 @@ namespace Umbraco.Core.Services : base(provider, repositoryFactory, logger, eventMessagesFactory) { } - public void Register(string url, int contentId) + public void Register(string url, Guid contentKey) { using (var uow = UowProvider.GetUnitOfWork()) using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) { - var redir = repo.Get(url, contentId); + var redir = repo.Get(url, contentKey); if (redir != null) redir.CreateDateUtc = DateTime.UtcNow; else - redir = new RedirectUrl { Url = url, ContentId = contentId }; + redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey }; repo.AddOrUpdate(redir); uow.Commit(); } @@ -39,7 +39,7 @@ namespace Umbraco.Core.Services } } - public void Delete(int id) + public void Delete(Guid id) { using (var uow = UowProvider.GetUnitOfWork()) using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) @@ -49,12 +49,12 @@ namespace Umbraco.Core.Services } } - public void DeleteContentRedirectUrls(int contentId) + public void DeleteContentRedirectUrls(Guid contentKey) { using (var uow = UowProvider.GetUnitOfWork()) using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) { - repo.DeleteContentUrls(contentId); + repo.DeleteContentUrls(contentKey); uow.Commit(); } } @@ -75,18 +75,16 @@ namespace Umbraco.Core.Services using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) { var rule = repo.GetMostRecentUrl(url); - uow.Commit(); return rule; } } - public IEnumerable GetContentRedirectUrls(int contentId) + public IEnumerable GetContentRedirectUrls(Guid contentKey) { using (var uow = UowProvider.GetUnitOfWork()) using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) { - var rules = repo.GetContentUrls(contentId); - uow.Commit(); + var rules = repo.GetContentUrls(contentKey); return rules; } } @@ -97,7 +95,6 @@ namespace Umbraco.Core.Services using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) { var rules = repo.GetAllUrls(pageIndex, pageSize, out total); - uow.Commit(); return rules; } } @@ -108,7 +105,15 @@ namespace Umbraco.Core.Services using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) { var rules = repo.GetAllUrls(rootContentId, pageIndex, pageSize, out total); - uow.Commit(); + return rules; + } + } + public IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var rules = repo.SearchUrls(searchTerm, pageIndex, pageSize, out total); return rules; } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index b92df5f1cf..036b5b979f 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -666,31 +666,12 @@ namespace Umbraco.Core return compare.Contains(compareTo, StringComparer.InvariantCultureIgnoreCase); } - /// - /// Determines if the string is a Guid - /// - /// - /// - /// + [Obsolete("Use Guid.TryParse instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static bool IsGuid(this string str, bool withHyphens) { - var isGuid = false; - - if (!String.IsNullOrEmpty(str)) - { - Regex guidRegEx; - if (withHyphens) - { - guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"); - } - else - { - guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){12}\}{0,1})$"); - } - isGuid = guidRegEx.IsMatch(str); - } - - return isGuid; + Guid g; + return Guid.TryParse(str, out g); } /// @@ -712,7 +693,7 @@ namespace Umbraco.Core /// public static object ParseInto(this string val, Type type) { - if (!String.IsNullOrEmpty(val)) + if (string.IsNullOrEmpty(val) == false) { TypeConverter tc = TypeDescriptor.GetConverter(type); return tc.ConvertFrom(val); @@ -750,6 +731,36 @@ namespace Umbraco.Core return stringBuilder.ToString(); } + /// + /// Converts the string to SHA1 + /// + /// referrs to itself + /// the md5 hashed string + public static string ToSHA1(this string stringToConvert) + { + //create an instance of the SHA1CryptoServiceProvider + var md5Provider = new SHA1CryptoServiceProvider(); + + //convert our string into byte array + var byteArray = Encoding.UTF8.GetBytes(stringToConvert); + + //get the hashed values created by our SHA1CryptoServiceProvider + var hashedByteArray = md5Provider.ComputeHash(byteArray); + + //create a StringBuilder object + var stringBuilder = new StringBuilder(); + + //loop to each each byte + foreach (var b in hashedByteArray) + { + //append it to our StringBuilder + stringBuilder.Append(b.ToString("x2").ToLower()); + } + + //return the hashed value + return stringBuilder.ToString(); + } + /// /// Decodes a string that was encoded with UrlTokenEncode diff --git a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs index dc3f4243e7..c3ef596ad0 100644 --- a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs @@ -26,5 +26,9 @@ namespace Umbraco.Core.Strings /// per content, in 1-to-1 multilingual configurations. Then there would be one /// url per culture. string GetUrlSegment(IContentBase content, CultureInfo culture); + + //TODO: For the 301 tracking, we need to add another extended interface to this so that + // the RedirectTrackingEventHandler can ask the IUrlSegmentProvider if the URL is changing. + // Currently the way it works is very hacky, see notes in: RedirectTrackingEventHandler.ContentService_Publishing } } diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index d8d828b23e..4e46c0ab5c 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 = new Sql().Select("*") + if (_lastId == 0) + { + var sql = new Sql().Select("COUNT(*)") + .From(_appContext.DatabaseContext.SqlSyntax); + + var count = _appContext.DatabaseContext.Database.ExecuteScalar(sql); + + //if there are instructions but we haven't synced, then a cold boot is necessary + if (count > 0) + _lastId = -1; + } + else + { + var sql = new Sql().Select("*") .From(_appContext.DatabaseContext.SqlSyntax) .Where(dto => dto.Id == _lastId); - var dtos = _appContext.DatabaseContext.Database.Fetch(sql); + var dtos = _appContext.DatabaseContext.Database.Fetch(sql); - if (dtos.Count == 0) - _lastId = -1; + //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 3e14fc7758..e6e91549fa 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -52,6 +52,10 @@ ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + ..\packages\ImageProcessor.2.4.4.0\lib\net45\ImageProcessor.dll + True + False ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll @@ -110,6 +114,7 @@ False ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll + @@ -305,6 +310,7 @@ + @@ -352,6 +358,7 @@ + @@ -419,9 +426,12 @@ + + + @@ -492,15 +502,19 @@ + + + + @@ -1083,7 +1097,6 @@ - diff --git a/src/Umbraco.Core/XmlHelper.cs b/src/Umbraco.Core/XmlHelper.cs index d45f0cac3b..66dcae7358 100644 --- a/src/Umbraco.Core/XmlHelper.cs +++ b/src/Umbraco.Core/XmlHelper.cs @@ -17,6 +17,35 @@ namespace Umbraco.Core /// 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. /// @@ -338,6 +367,9 @@ namespace Umbraco.Core /// 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; @@ -352,11 +384,37 @@ namespace Umbraco.Core /// 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 /// @@ -366,11 +424,37 @@ namespace Umbraco.Core /// 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/packages.config b/src/Umbraco.Core/packages.config index b69be5b5cc..155fead690 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -2,6 +2,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 d28a8c44e9..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; 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/CodeFirst/CodeFirstTests.cs b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs index ee9080b094..c9550f89c1 100644 --- a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs +++ b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using NUnit.Framework; @@ -60,7 +61,7 @@ namespace Umbraco.Tests.CodeFirst var result = SerializationService.ToStream(contentType.Value); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] @@ -91,7 +92,7 @@ namespace Umbraco.Tests.CodeFirst var result = SerializationService.ToStream(contentType.Value); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] diff --git a/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs b/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs index 08dbd89fca..5df7849430 100644 --- a/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs +++ b/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Umbraco.Core; @@ -210,11 +211,11 @@ namespace Umbraco.Tests.CodeFirst.Definitions { var field = fields[sortOrder[i]]; list.Add(field.ContentType.Value); - Console.WriteLine(field.Alias); + Debug.Print(field.Alias); if (field.DependsOn != null) foreach (var item in field.DependsOn) { - Console.WriteLine(" -{0}", item); + Debug.Print(" -{0}", item); } } list.Reverse(); diff --git a/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs b/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs index 1e2302087b..5fe2efaa2c 100644 --- a/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs +++ b/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; @@ -84,7 +85,7 @@ namespace Umbraco.Tests.CodeFirst }; var type = new AutoPublishedContentType(0, "anything", propertyTypes); PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; - Console.WriteLine("INIT STRONG {0}", + Debug.Print("INIT STRONG {0}", PublishedContentType.Get(PublishedItemType.Content, "anything") .PropertyTypes.Count()); } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs index 47b5e15b69..1e568c608e 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs @@ -28,5 +28,11 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.WebRouting.DisableFindContentByIdPath == false); } + + [Test] + public void DisableRedirectUrlTracking() + { + Assert.IsTrue(SettingsSection.WebRouting.DisableRedirectUrlTracking == false); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs index 41414cb81a..bf5100815f 100644 --- a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs +++ b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs @@ -726,8 +726,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/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 035a76d052..672b9022e5 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Web; namespace Umbraco.Tests.FrontEnd @@ -48,5 +45,38 @@ namespace Umbraco.Tests.FrontEnd Assert.AreEqual("Hello world, this is some text with…", result); } + [Test] + public void Create_Encrypted_RouteString_From_Anonymous_Object() + { + var additionalRouteValues = new + { + key1 = "value1", + key2 = "value2", + Key3 = "Value3", + keY4 = "valuE4" + }; + var encryptedRouteString = UmbracoHelper.CreateEncryptedRouteString("FormController", "FormAction", "", additionalRouteValues); + var result = encryptedRouteString.DecryptWithMachineKey(); + var expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; + + Assert.AreEqual(expectedResult, result); + } + + [Test] + public void Create_Encrypted_RouteString_From_Dictionary() + { + var additionalRouteValues = new Dictionary() + { + {"key1", "value1"}, + {"key2", "value2"}, + {"Key3", "Value3"}, + {"keY4", "valuE4"} + }; + var encryptedRouteString = UmbracoHelper.CreateEncryptedRouteString("FormController", "FormAction", "", additionalRouteValues); + var result = encryptedRouteString.DecryptWithMachineKey(); + var expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; + + Assert.AreEqual(expectedResult, result); + } } } diff --git a/src/Umbraco.Tests/LibraryTests.cs b/src/Umbraco.Tests/LibraryTests.cs index dca6215d90..086395f456 100644 --- a/src/Umbraco.Tests/LibraryTests.cs +++ b/src/Umbraco.Tests/LibraryTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -44,7 +45,7 @@ namespace Umbraco.Tests }; var type = new AutoPublishedContentType(0, "anything", propertyTypes); PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; - Console.WriteLine("INIT LIB {0}", + Debug.Print("INIT LIB {0}", PublishedContentType.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/ParallelForwarderTest.cs b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs index da5c58c0a9..88471ab650 100644 --- a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs +++ b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs @@ -120,7 +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); + Debug.Print("Logged {0} errors in {1}ms", testSize, watch.ElapsedMilliseconds); } [Test] @@ -171,7 +171,7 @@ 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); } [Test, Explicit("Long-running")] @@ -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/Migrations/AlterMigrationTests.cs b/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs index 94e8802ca4..4e38f427e5 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.Linq; using Moq; using NUnit.Framework; @@ -45,11 +46,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 eb3c36ce42..6d49205adb 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.Linq; using Moq; using NUnit.Framework; @@ -87,7 +88,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 55d60eed66..8b51037d14 100644 --- a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs @@ -1,10 +1,21 @@ 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; +using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; +using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Tests.Migrations { @@ -70,14 +81,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); @@ -86,9 +99,49 @@ 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(); + + //Setup the MigrationRunner + var migrationRunner = new MigrationRunner( + Mock.Of(), + logger, + new SemVersion(7, 4, 0), + new SemVersion(7, 5, 0), + GlobalSettings.UmbracoMigrationName, + + //pass in explicit migrations + new DeleteRedirectUrlTable(SqlSyntax, logger), + new AddRedirectUrlTable(SqlSyntax, logger) + ); + + var db = new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Logger); + + var upgraded = migrationRunner.Execute(db, DatabaseProviders.SqlServerCE, true); + Assert.IsTrue(upgraded); + } + + [Migration("7.5.0", 99, GlobalSettings.UmbracoMigrationName)] + public class DeleteRedirectUrlTable : MigrationBase + { + public DeleteRedirectUrlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + Delete.Table("umbracoRedirectUrl"); + } + + public override void Down() + { } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs index 8d8682ecc5..51822c8098 100644 --- a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs +++ b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data.SqlServerCe; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using Moq; @@ -89,7 +90,7 @@ namespace Umbraco.Tests.Migrations foreach (var expression in context.Expressions) { - Console.WriteLine(expression.ToString()); + Debug.Print(expression.ToString()); } Assert.That(migrations.Count(), Is.EqualTo(12)); @@ -99,7 +100,7 @@ namespace Umbraco.Tests.Migrations public UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } diff --git a/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs index bdcb55a4a7..9026c3769f 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Server = 169.254.120.3; Database = upgradetest; Uid = umbraco; Pwd = umbraco", "MySql.Data.MySqlClient", Mock.Of()); + return new UmbracoDatabase("Server = 169.254.120.3; Database = upgradetest; Uid = umbraco; Pwd = umbraco", Constants.DatabaseProviders.MySql, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs index 39ff745e0c..2ea7a40e57 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using Semver; using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; @@ -64,7 +65,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs index 0f8e0543ad..f7080b7bc0 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs @@ -5,6 +5,7 @@ using System.IO; using Moq; using NUnit.Framework; using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; @@ -42,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 { @@ -67,7 +70,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs index 8b5fc13114..088ea5eedf 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; @@ -24,7 +25,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", "System.Data.SqlClient", Mock.Of()); + return new UmbracoDatabase(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", Constants.DatabaseProviders.SqlServer, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs index e735e2330c..a375eb6850 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Moq; using NUnit.Framework; using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; @@ -70,8 +71,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(); + } SqlSyntaxContext.SqlSyntaxProvider = new SqlCeSyntaxProvider(); } @@ -101,7 +104,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } public string GetDatabaseSpecificSqlScript() diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 6bc61649a5..4244188697 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; @@ -382,7 +383,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 0589d7d173..9d368ef886 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 d066fbacaf..f5c37220fd 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 a8c15e9a5e..8119753c8b 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; @@ -212,7 +213,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 24c0b64c88..ec6761e0e2 100644 --- a/src/Umbraco.Tests/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests/Models/StylesheetTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -112,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/BaseTableByTableTest.cs b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs index e9b58ffcae..bed5582609 100644 --- a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Persistence var dbContext = new DatabaseContext( new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, _logger), - _logger, SqlSyntaxProvider, "System.Data.SqlServerCe.4.0"); + _logger, SqlSyntaxProvider, Constants.DatabaseProviders.SqlCe); var repositoryFactory = new RepositoryFactory(cacheHelper, _logger, SqlSyntaxProvider, SettingsForTests.GenerateMockSettings()); diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 5883562f3b..94f0010201 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence { _dbContext = new DatabaseContext( new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Mock.Of()), - Mock.Of(), new SqlCeSyntaxProvider(), "System.Data.SqlServerCe.4.0"); + Mock.Of(), new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); //unfortunately we have to set this up because the PetaPocoExtensions require singleton access ApplicationContext.Current = new ApplicationContext( @@ -81,10 +81,13 @@ namespace Umbraco.Tests.Persistence //by default the conn string is: Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1; //we'll just replace the sdf file with our custom one: //Create the Sql CE database - var engine = new SqlCeEngine(settings.ConnectionString.Replace("UmbracoPetaPocoTests", "DatabaseContextTests")); - engine.CreateDatabase(); + var connString = settings.ConnectionString.Replace("UmbracoPetaPocoTests", "DatabaseContextTests"); + using (var engine = new SqlCeEngine(connString)) + { + engine.CreateDatabase(); + } - var dbFactory = new DefaultDatabaseFactory(engine.LocalConnectionString, "System.Data.SqlServerCe.4.0", Mock.Of()); + var dbFactory = new DefaultDatabaseFactory(connString, Constants.DatabaseProviders.SqlCe, Mock.Of()); //re-map the dbcontext to the new conn string _dbContext = new DatabaseContext( dbFactory, diff --git a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs index 841db8198c..6d5f0987eb 100644 --- a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs +++ b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs @@ -1,6 +1,7 @@ using System.Data.SqlClient; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -13,7 +14,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling public void PetaPocoConnection_Cant_Connect_To_SqlDatabase_With_Invalid_User() { // Arrange - const string providerName = "System.Data.SqlClient"; + const string providerName = Constants.DatabaseProviders.SqlServer; const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco"; var factory = new DefaultDatabaseFactory(connectionString, providerName, Mock.Of()); var database = factory.CreateDatabase(); @@ -27,7 +28,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling public void PetaPocoConnection_Cant_Connect_To_SqlDatabase_Because_Of_Network() { // Arrange - const string providerName = "System.Data.SqlClient"; + const string providerName = Constants.DatabaseProviders.SqlServer; const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco"; var factory = new DefaultDatabaseFactory(connectionString, providerName, Mock.Of()); var database = factory.CreateDatabase(); diff --git a/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs b/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs index 17d12de478..4b3b9ef51d 100644 --- a/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Persistence base.Initialize(); _database = new Database("Server = 169.254.120.3; Database = testdb; Uid = umbraco; Pwd = umbraco", - "MySql.Data.MySqlClient"); + Constants.DatabaseProviders.MySql); } [TearDown] diff --git a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs index d33148adc7..0a15994785 100644 --- a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs +++ b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -46,7 +47,7 @@ namespace Umbraco.Tests.Persistence double totalBytes1; IEnumerable keys; - Console.Write(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); + Debug.Print(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); result.Add(new Tuple>(totalBytes1, keys.Count(), keys)); } @@ -54,14 +55,14 @@ namespace Umbraco.Tests.Persistence for (int index = 0; index < result.Count; index++) { var tuple = result[index]; - Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); + Debug.Print("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); if (index != 0) { - Console.WriteLine("----------------DIFFERENCE---------------------"); + Debug.Print("----------------DIFFERENCE---------------------"); var diff = tuple.Item3.Except(result[index - 1].Item3); foreach (var d in diff) { - Console.WriteLine(d); + Debug.Print(d); } } @@ -87,13 +88,13 @@ namespace Umbraco.Tests.Persistence QueryStuff(id1, id2, id3, alias); var count1 = managedCache.GetCache().GetCount(); - Console.WriteLine("Keys = " + count1); + Debug.Print("Keys = " + count1); Assert.Greater(count1, 0); Thread.Sleep(10000); var count2 = managedCache.GetCache().GetCount(); - Console.WriteLine("Keys = " + count2); + Debug.Print("Keys = " + count2); Assert.Less(count2, count1); } diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index 6e6d5251a8..c015410dfa 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 NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -42,7 +43,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -79,7 +80,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -97,7 +98,7 @@ namespace Umbraco.Tests.Persistence.Querying .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) .Where("([umbracoNode].[id] = @0)", 1050) .Where("([cmsContentVersion].[VersionId] = @0)", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")) - .OrderBy("[cmsContentVersion].[VersionDate] DESC"); + .OrderBy("([cmsContentVersion].[VersionDate]) DESC"); var sql = new Sql(); sql.Select("*") @@ -120,7 +121,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -151,7 +152,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 5768f81242..8619692c52 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 NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -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] @@ -81,7 +82,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -103,7 +104,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -127,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -157,7 +158,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 445b5b3e36..eede4adaa4 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 NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -36,7 +37,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 2f6c903d14..4529343811 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 NUnit.Framework; @@ -23,7 +24,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]); @@ -37,7 +38,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("upper([umbracoNode].[path]) LIKE upper(@0)", result); Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -51,7 +52,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("([umbracoNode].[parentID] = @0)", result); Assert.AreEqual(-1, modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -64,7 +65,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("([umbracoUser].[userLogin] = @0)", result); Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -77,7 +78,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("upper([umbracoUser].[userLogin]) = upper(@0)", result); Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -93,7 +94,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("upper(`umbracoUser`.`userLogin`) = upper(@0)", result); Assert.AreEqual("mydomain\\myuser", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -110,11 +111,26 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper(); 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]); } + [Test] + public void Sql_Replace_Mapped() + { + Expression> predicate = user => user.Username.Replace("@world", "@test") == "hello@test.com"; + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var result = modelToSqlExpressionHelper.Visit(predicate); + + Debug.Print("Model to Sql ExpressionHelper: \n" + result); + + Assert.AreEqual("(replace([umbracoUser].[userLogin], @1, @2) = @0)", result); + Assert.AreEqual("hello@test.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); + Assert.AreEqual("@world", modelToSqlExpressionHelper.GetSqlParameters()[1]); + Assert.AreEqual("@test", modelToSqlExpressionHelper.GetSqlParameters()[2]); + } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs index 57b2ef7503..37a12df6a3 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 NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -39,7 +40,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 efed0e4e0f..bb417a6b94 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 NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -36,7 +37,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/PetaPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs index 7eed26c2d0..39f3f484d0 100644 --- a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -176,7 +177,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -195,7 +196,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -209,7 +210,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -223,7 +224,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -237,7 +238,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -257,7 +258,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 dcc1b298a9..54b9a150f9 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 NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -38,7 +39,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] @@ -65,7 +66,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] @@ -91,7 +92,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] @@ -121,7 +122,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 ce3ce3f22a..abcb5b3d6a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -44,6 +44,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, SqlSyntax, contentTypeRepository); + return ctRepository; + } + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) { TemplateRepository tr; @@ -223,6 +231,67 @@ namespace Umbraco.Tests.Persistence.Repositories } } + /// + /// 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 = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + DataTypeDefinitionRepository dataTypeDefinitionRepository; + using (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.Commit(); + + 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.Commit(); + + // 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.Commit(); + + // 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() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index c8ffda7aec..bb13f055c9 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; @@ -333,7 +334,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/RedirectUrlRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs index c511ed21da..084d43d24c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var rurl = new RedirectUrl { - ContentId = _textpage.Id, + ContentKey = _textpage.Key, Url = "blah" }; repo.AddOrUpdate(rurl); @@ -62,12 +62,14 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); + Assert.AreNotEqual(_textpage.Id, _otherpage.Id); + using (var uow = provider.GetUnitOfWork()) using (var repo = CreateRepository(uow)) { var rurl = new RedirectUrl { - ContentId = _textpage.Id, + ContentKey = _textpage.Key, Url = "blah" }; repo.AddOrUpdate(rurl); @@ -75,10 +77,16 @@ namespace Umbraco.Tests.Persistence.Repositories 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 { - ContentId = _otherpage.Id, - Url = "blah" + ContentKey = _otherpage.Key, + Url = "blah", + CreateDateUtc = rurl.CreateDateUtc.AddSeconds(1) // ensure time difference }; repo.AddOrUpdate(rurl); uow.Commit(); @@ -107,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var rurl = new RedirectUrl { - ContentId = _textpage.Id, + ContentKey = _textpage.Key, Url = "blah" }; repo.AddOrUpdate(rurl); @@ -115,10 +123,13 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreNotEqual(0, rurl.Id); + // fixme - goes too fast and bam, errors, first is blah + rurl = new RedirectUrl { - ContentId = _textpage.Id, - Url = "durg" + ContentKey = _textpage.Key, + Url = "durg", + CreateDateUtc = rurl.CreateDateUtc.AddSeconds(1) // ensure time difference }; repo.AddOrUpdate(rurl); uow.Commit(); @@ -129,7 +140,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var uow = provider.GetUnitOfWork()) using (var repo = CreateRepository(uow)) { - var rurls = repo.GetContentUrls(_textpage.Id).ToArray(); + var rurls = repo.GetContentUrls(_textpage.Key).ToArray(); uow.Commit(); Assert.AreEqual(2, rurls.Length); @@ -148,7 +159,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var rurl = new RedirectUrl { - ContentId = _textpage.Id, + ContentKey = _textpage.Key, Url = "blah" }; repo.AddOrUpdate(rurl); @@ -158,7 +169,7 @@ namespace Umbraco.Tests.Persistence.Repositories rurl = new RedirectUrl { - ContentId = _otherpage.Id, + ContentKey = _otherpage.Key, Url = "durg" }; repo.AddOrUpdate(rurl); @@ -170,10 +181,10 @@ namespace Umbraco.Tests.Persistence.Repositories using (var uow = provider.GetUnitOfWork()) using (var repo = CreateRepository(uow)) { - repo.DeleteContentUrls(_textpage.Id); + repo.DeleteContentUrls(_textpage.Key); uow.Commit(); - var rurls = repo.GetContentUrls(_textpage.Id); + var rurls = repo.GetContentUrls(_textpage.Key); Assert.AreEqual(0, rurls.Count()); } @@ -190,27 +201,29 @@ namespace Umbraco.Tests.Persistence.Repositories { //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); - contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); + 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 = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0"); - ServiceContext.ContentService.Save(_textpage, 0); + _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 = new Guid("FF11402B-7E53-4654-81A7-462AC2108059"); - ServiceContext.ContentService.Save(_subpage, 0); + _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); - ServiceContext.ContentService.Save(_otherpage, 0); + _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, 0); + ServiceContext.ContentService.Save(_trashed); } } } diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index d7115461db..474735979e 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -40,11 +40,13 @@ namespace Umbraco.Tests.Persistence } //Create the Sql CE database - var engine = new SqlCeEngine("Datasource=|DataDirectory|test.sdf;Flush Interval=1;"); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine("Datasource=|DataDirectory|test.sdf;Flush Interval=1;")) + { + engine.CreateDatabase(); + } _database = new Database("Datasource=|DataDirectory|test.sdf;Flush Interval=1;", - "System.Data.SqlServerCe.4.0"); + Constants.DatabaseProviders.SqlCe); } [TearDown] diff --git a/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs index f781d38447..0b081628d1 100644 --- a/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Persistence base.Initialize(); _database = new Database(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", - "System.Data.SqlClient"); + Constants.DatabaseProviders.SqlServer); } [TearDown] diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs index 67c29581a4..8126aa5e36 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using Moq; using NUnit.Framework; @@ -34,16 +35,16 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider var indexes = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.Indexes); var keys = SqlSyntaxContext.SqlSyntaxProvider.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/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index 2ec7688a6a..e960f50799 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 NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -57,16 +58,16 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, var indexes = sqlSyntax.Format(definition.Indexes); var keys = 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/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 316d957a0c..9ae9519697 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); @@ -190,7 +190,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 8d86e806a6..70188eaa9d 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -14,6 +16,7 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -876,6 +879,46 @@ namespace Umbraco.Tests.Services Assert.That(content.Published, Is.True); } + [Test] + public void Can_Publish_Content_WithEvents() + { + ContentService.Publishing += ContentServiceOnPublishing; + + // tests that during 'publishing' event, what we get from the repo is the 'old' content, + // because 'publishing' fires before the 'saved' event ie before the content is actually + // saved + + try + { + var contentService = ServiceContext.ContentService; + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); + Assert.AreEqual("Home", content.Name); + + content.Name = "foo"; + var published = contentService.Publish(content, 0); + + Assert.That(published, Is.True); + Assert.That(content.Published, Is.True); + + var e = ServiceContext.ContentService.GetById(content.Id); + Assert.AreEqual("foo", e.Name); + } + finally + { + ContentService.Publishing -= ContentServiceOnPublishing; + } + } + + private void ContentServiceOnPublishing(IPublishingStrategy sender, PublishEventArgs args) + { + Assert.AreEqual(1, args.PublishedEntities.Count()); + var entity = args.PublishedEntities.First(); + Assert.AreEqual("foo", entity.Name); + + var e = ServiceContext.ContentService.GetById(entity.Id); + Assert.AreEqual("Home", e.Name); + } + [Test] public void Can_Publish_Only_Valid_Content() { @@ -1587,7 +1630,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; @@ -1601,7 +1644,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 742205904f..bf752aa601 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -7,14 +7,14 @@ using Umbraco.Core; using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; - +using Umbraco.Core.Services; using Umbraco.Tests.CodeFirst.TestModels.Composition; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services { - + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture, RequiresSTA] public class ContentTypeServiceTests : BaseServiceTest @@ -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.DeletedContentType += (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.DeletedContentType += (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.DeletedContentType += (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/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 9e2b440815..c9cf4e6d5b 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; @@ -33,6 +34,34 @@ namespace Umbraco.Tests.Services base.TearDown(); } + [Test] + public void Can_Create_Member() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.AreNotEqual(0, member.Id); + var foundMember = ServiceContext.MemberService.GetById(member.Id); + Assert.IsNotNull(foundMember); + Assert.AreEqual("test@test.com", foundMember.Email); + } + + [Test] + public void Can_Create_Member_With_Long_TLD_In_Email() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.marketing", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.AreNotEqual(0, member.Id); + var foundMember = ServiceContext.MemberService.GetById(member.Id); + Assert.IsNotNull(foundMember); + Assert.AreEqual("test@test.marketing", foundMember.Email); + } + [Test] public void Can_Create_Role() { diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs index 72b1323e65..05ae7d0ad4 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/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 2a4ce8b5ea..69f39392d6 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Services protected override string GetDbProviderName() { - return "System.Data.SqlClient"; + return Constants.DatabaseProviders.SqlServer; } diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index ed87493c01..00e744c28b 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Services //it is multi-threaded. _dbFactory = new PerThreadDatabaseFactory(Logger); //overwrite the local object - ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), "System.Data.SqlServerCe.4.0"); + ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); //disable cache var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index ec7bbe3c72..58b0451995 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; @@ -369,9 +370,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 996b7e2fff..28649457dd 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -1,22 +1,16 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Configuration; using System.Data.SqlServerCe; using System.IO; -using System.Linq; using System.Web.Routing; using System.Xml; -using Moq; using NUnit.Framework; using SQLCE4Umbraco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; @@ -27,7 +21,6 @@ using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; -using Umbraco.Web.Routing; using Umbraco.Web.Security; using umbraco.BusinessLogic; using Umbraco.Core.Events; @@ -35,9 +28,10 @@ using Umbraco.Core.Events; namespace Umbraco.Tests.TestHelpers { /// - /// Use this abstract class for tests that requires a Sql Ce database populated with the umbraco db schema. - /// The PetaPoco 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 { @@ -48,15 +42,15 @@ namespace Umbraco.Tests.TestHelpers private bool _firstTestInFixture = true; //Used to flag if its the first test in the current session - private bool _isFirstRunInTestSession = false; + private bool _isFirstRunInTestSession; //Used to flag if its the first test in the current fixture - private bool _isFirstTestInFixture = false; + private bool _isFirstTestInFixture; private ApplicationContext _appContext; private string _dbPath; //used to store (globally) the pre-built db with schema and initial data - private static Byte[] _dbBytes; + private static byte[] _dbBytes; private DefaultDatabaseFactory _dbFactory; [SetUp] @@ -71,7 +65,7 @@ namespace Umbraco.Tests.TestHelpers GetDbConnectionString(), GetDbProviderName(), Logger); - + _dbFactory.ResetForTests(); base.Initialize(); @@ -90,18 +84,15 @@ namespace Umbraco.Tests.TestHelpers protected override ApplicationContext CreateApplicationContext() { - //disable cache - var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); - - var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); + var repositoryFactory = new RepositoryFactory(CacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); var evtMsgs = new TransientMessagesFactory(); _appContext = new ApplicationContext( //assign the db context - new DatabaseContext(_dbFactory, Logger, SqlSyntax, "System.Data.SqlServerCe.4.0"), + new DatabaseContext(_dbFactory, Logger, SqlSyntax, GetDbProviderName()), //assign the service context - new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(_dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), cacheHelper, Logger, evtMsgs), - cacheHelper, + new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(_dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs), + CacheHelper, ProfilingLogger) { IsReady = true @@ -111,7 +102,7 @@ namespace Umbraco.Tests.TestHelpers protected virtual ISqlSyntaxProvider SqlSyntax { - get { return new SqlCeSyntaxProvider(); } + get { return GetSyntaxProvider(); } } /// @@ -121,11 +112,16 @@ namespace Umbraco.Tests.TestHelpers { get { - var att = this.GetType().GetCustomAttribute(false); + var att = GetType().GetCustomAttribute(false); return att != null ? att.Behavior : DatabaseBehavior.NoDatabasePerFixture; } } + protected virtual ISqlSyntaxProvider GetSyntaxProvider() + { + return new SqlCeSyntaxProvider(); + } + protected virtual string GetDbProviderName() { return "System.Data.SqlServerCe.4.0"; @@ -168,7 +164,7 @@ namespace Umbraco.Tests.TestHelpers || (DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerTest || DatabaseTestBehavior == DatabaseBehavior.EmptyDbFilePerTest) || (_isFirstTestInFixture && DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerFixture)) { - + using (ProfilingLogger.TraceDuration("Remove database file")) { RemoveDatabaseFile(ex => @@ -189,8 +185,10 @@ namespace Umbraco.Tests.TestHelpers } else { - var engine = new SqlCeEngine(settings.ConnectionString); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(settings.ConnectionString)) + { + engine.CreateDatabase(); + } } } @@ -248,7 +246,7 @@ namespace Umbraco.Tests.TestHelpers //Create the umbraco database and its base data schemaHelper.CreateDatabaseSchema(false, ApplicationContext); - //close the connections, we're gonna read this baby in as a byte array so we don't have to re-initialize the + //close the connections, we're gonna read this baby in as a byte array so we don't have to re-initialize the // damn db for each test CloseDbConnections(); @@ -284,7 +282,7 @@ namespace Umbraco.Tests.TestHelpers private void CloseDbConnections() { - //Ensure that any database connections from a previous test is disposed. + //Ensure that any database connections from a previous test is disposed. //This is really just double safety as its also done in the TearDown. if (ApplicationContext != null && DatabaseContext != null && DatabaseContext.Database != null) DatabaseContext.Database.Dispose(); @@ -306,23 +304,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(Action onFail = null) { CloseDbConnections(); - string path = TestHelper.CurrentAssemblyDirectory; + var path = TestHelper.CurrentAssemblyDirectory; try { string filePath = string.Concat(path, "\\UmbracoPetaPocoTests.sdf"); @@ -337,9 +333,7 @@ namespace Umbraco.Tests.TestHelpers //We will swallow this exception! That's because a sub class might require further teardown logic. if (onFail != null) - { onFail(ex); - } } } @@ -397,7 +391,7 @@ namespace Umbraco.Tests.TestHelpers protected virtual string GetXmlContent(int templateId) { return @" - @@ -410,7 +404,7 @@ namespace Umbraco.Tests.TestHelpers 1 This is some content]]> - + diff --git a/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs b/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs index e31f962122..ef039630f3 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs @@ -51,14 +51,16 @@ namespace Umbraco.Tests.TestHelpers var connectionString = string.Format(@"Data Source={0}", databaseDataPath); //Create the Sql CE database - var engine = new SqlCeEngine(connectionString); - if (File.Exists(databaseDataPath) == false) - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(connectionString)) + { + if (File.Exists(databaseDataPath) == false) + engine.CreateDatabase(); + } var syntaxProvider = new SqlCeSyntaxProvider(); SqlSyntaxContext.SqlSyntaxProvider = syntaxProvider; - _database = new UmbracoDatabase(connectionString, "System.Data.SqlServerCe.4.0", Mock.Of()); + _database = new UmbracoDatabase(connectionString, Constants.DatabaseProviders.SqlCe, Mock.Of()); // First remove anything in the database var creation = new DatabaseSchemaCreation(_database, Mock.Of(), syntaxProvider); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 84b109a66a..642bad4a82 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -1,5 +1,4 @@ -using System.Collections; -using System.Collections.Generic; +using System; using System.IO; using System.Reflection; using AutoMapper; @@ -9,7 +8,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models.Mapping; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; @@ -20,18 +18,16 @@ using Umbraco.Web; using Umbraco.Web.Models.Mapping; using umbraco.BusinessLogic; using Umbraco.Core.Events; -using ObjectExtensions = Umbraco.Core.ObjectExtensions; 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() { @@ -65,16 +61,18 @@ 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; - ResetPluginManager(); + // reset plugin manager + ResetPluginManager(); } private static readonly object Locker = new object(); @@ -85,7 +83,7 @@ namespace Umbraco.Tests.TestHelpers { if (LegacyPropertyEditorIdToAliasConverter.Count() == 0) { - //Create the legacy prop-eds mapping + // create the legacy prop-eds mapping LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); } } @@ -100,7 +98,7 @@ namespace Umbraco.Tests.TestHelpers /// private void InitializeMappers() { - if (this.GetType().GetCustomAttribute(false) != null) + if (GetType().GetCustomAttribute(false) != null) { Mapper.Initialize(configuration => { @@ -121,7 +119,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 { @@ -141,7 +139,12 @@ namespace Umbraco.Tests.TestHelpers protected virtual void SetupCacheHelper() { - CacheHelper = CacheHelper.CreateDisabledCacheHelper(); + CacheHelper = CreateCacheHelper(); + } + + protected virtual CacheHelper CreateCacheHelper() + { + return CacheHelper.CreateDisabledCacheHelper(); } /// @@ -156,12 +159,12 @@ namespace Umbraco.Tests.TestHelpers protected virtual ApplicationContext CreateApplicationContext() { var sqlSyntax = new SqlCeSyntaxProvider(); - var repoFactory = new RepositoryFactory(CacheHelper.CreateDisabledCacheHelper(), Logger, sqlSyntax, SettingsForTests.GenerateMockSettings()); + var repoFactory = new RepositoryFactory(CacheHelper, Logger, sqlSyntax, SettingsForTests.GenerateMockSettings()); var evtMsgs = new TransientMessagesFactory(); var applicationContext = new ApplicationContext( //assign the db context - new DatabaseContext(new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Logger), Logger, sqlSyntax, "System.Data.SqlServerCe.4.0"), + new DatabaseContext(new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Logger), Logger, sqlSyntax, Constants.DatabaseProviders.SqlCe), //assign the service context new ServiceContext(repoFactory, new PetaPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs), CacheHelper, @@ -216,7 +219,9 @@ namespace Umbraco.Tests.TestHelpers { get { return ProfilingLogger.Logger; } } + protected ProfilingLogger ProfilingLogger { get; private set; } + protected CacheHelper CacheHelper { get; private set; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index 3452b55f39..db6919b24e 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/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/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 1959330487..a38e951434 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -60,7 +60,7 @@ True - ..\packages\Examine.0.1.69.0-beta\lib\Examine.dll + ..\packages\Examine.0.1.69.0\lib\Examine.dll True @@ -178,6 +178,8 @@ + + diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index d2377954be..cc0fc5f214 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -2,7 +2,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/bower.json b/src/Umbraco.Web.UI.Client/bower.json index d1397b5d4e..0f3eea3291 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -20,8 +20,9 @@ "underscore": "~1.7.0", "rgrove-lazyload": "*", "bootstrap-social": "~4.8.0", - "jquery": "2.0.3", + "jquery": "2.2.4", "jquery-ui": "1.11.4", + "jquery-migrate": "1.4.0", "angular-dynamic-locale": "0.1.28", "ng-file-upload": "~7.3.8", "tinymce": "~4.1.10", diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index d5b785c54c..0a28d9efaf 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -484,12 +484,17 @@ module.exports = function (grunt) { files: ['css/font-awesome.min.css', 'fonts/*'] }, "jquery": { - files: ['jquery.min.js', 'jquery.min.map'] + keepExpandedHierarchy: false, + files: ['dist/jquery.min.js', 'dist/jquery.min.map'] }, 'jquery-ui': { keepExpandedHierarchy: false, files: ['jquery-ui.min.js'] }, + 'jquery-migrate': { + keepExpandedHierarchy: false, + files: ['jquery-migrate.min.js'] + }, 'tinymce': { files: ['plugins/**', 'themes/**', 'tinymce.min.js'] }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 0692a10e90..5dd7d266df 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -64,6 +64,7 @@ angular.module("umbraco.directives") await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { angular.forEach(rules, function (rule) { var r = {}; + var split = ""; r.title = rule.name; if (rule.selector[0] === ".") { r.inline = "span"; @@ -74,6 +75,14 @@ angular.module("umbraco.directives") // since only one element can have one id. r.inline = "span"; r.attributes = { id: rule.selector.substring(1) }; + }else if (rule.selector[0] !== "." && rule.selector.indexOf(".") > -1) { + split = rule.selector.split("."); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); + }else if (rule.selector[0] !== "#" && rule.selector.indexOf("#") > -1) { + split = rule.selector.split("#"); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); }else { r.block = rule.selector; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index a3b2fab00f..4ba4cf96bb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -43,7 +43,6 @@ function treeSearchBox(localizationService, searchService, $q) { //a canceler exists, so perform the cancelation operation and reset if (canceler) { - console.log("CANCELED!"); canceler.resolve(); canceler = $q.defer(); } 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/umblistviewsettings.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js index ae9da02237..f21f7b8d3d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js @@ -1,7 +1,7 @@ (function() { 'use strict'; - function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper) { + function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper, listViewPrevalueHelper) { function link(scope, el, attr, ctrl) { @@ -20,6 +20,7 @@ scope.dataType = dataType; + listViewPrevalueHelper.setPrevalues(dataType.preValues); scope.customListViewCreated = checkForCustomListView(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 7d2da34988..e9e7395761 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -138,6 +138,22 @@ Use this directive to generate a thumbnail grid of media items. if (!item.isFolder) { item.thumbnail = mediaHelper.resolveFile(item, true); item.image = mediaHelper.resolveFile(item, false); + + var fileProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoFile"); + }); + + if (fileProp && fileProp.value) { + item.file = fileProp.value; + } + + var extensionProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoExtension"); + }); + + if (extensionProp && extensionProp.value) { + item.extension = extensionProp.value; + } } } 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/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index a9296acc37..914b601249 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -109,16 +109,6 @@ function entityResource($q, $http, umbRequestHelper) { [{ id: id}, {type: type }])), 'Failed to retrieve entity data for id ' + id); }, - - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{query: query},{ nodeContextId: nodeContextId}, {type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, /** * @ngdoc method @@ -168,7 +158,41 @@ function entityResource($q, $http, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.entityResource#getEntityById + * @name umbraco.resources.entityResource#getByQuery + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity from a given xpath + * + * ##usage + *
+         * //get content by xpath
+         * entityResource.getByQuery("$current", -1, "Document")
+         *    .then(function(ent) {
+         *        var myDoc = ent; 
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {string} query xpath to use in query + * @param {Int} nodeContextId id id to start from + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), + 'Failed to retrieve entity data for query ' + query); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAll * @methodOf umbraco.resources.entityResource * * @description @@ -260,7 +284,7 @@ function entityResource($q, $http, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.entityResource#searchMedia + * @name umbraco.resources.entityResource#search * @methodOf umbraco.resources.entityResource * * @description 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 index d9307c7c28..053aaf1394 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js @@ -5,6 +5,7 @@ **/ function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) { + //var baseurl = "http://localhost:24292/webapi/packages/v1"; var baseurl = "https://our.umbraco.org/webapi/packages/v1"; return { @@ -33,17 +34,17 @@ function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHel } return umbRequestHelper.resourcePromise( - $http.get(baseurl + "?pageIndex=0&pageSize=" + maxResults + "&category=" + category + "&order=Popular"), + $http.get(baseurl + "?pageIndex=0&pageSize=" + maxResults + "&category=" + category + "&order=Popular&version=" + Umbraco.Sys.ServerVariables.application.version), 'Failed to query packages'); }, - search: function (pageIndex, pageSize, category, query, canceler) { + search: function (pageIndex, pageSize, orderBy, category, query, canceler) { var httpConfig = {}; if (canceler) { httpConfig["timeout"] = canceler; } - + if (category === undefined) { category = ""; } @@ -51,8 +52,11 @@ function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHel query = ""; } + //order by score if there is nothing set + var order = !orderBy ? "&order=Default" : ("&order=" + orderBy); + return umbRequestHelper.resourcePromise( - $http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query), + $http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query + order + "&version=" + Umbraco.Sys.ServerVariables.application.version), httpConfig, 'Failed to query packages'); } 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 33889e35e4..c9a501ba24 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 @@ -24,6 +24,24 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { '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( @@ -93,7 +111,7 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "packageInstallApiBaseUrl", "Import"), package), - 'Failed to create package manifest for zip file '); + 'Failed to install package. Error during the step "Import" '); }, installFiles: function (package) { @@ -102,7 +120,7 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "packageInstallApiBaseUrl", "InstallFiles"), package), - 'Failed to create package manifest for zip file '); + 'Failed to install package. Error during the step "InstallFiles" '); }, installData: function (package) { @@ -112,7 +130,7 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "packageInstallApiBaseUrl", "InstallData"), package), - 'Failed to create package manifest for zip file '); + 'Failed to install package. Error during the step "InstallData" '); }, cleanUp: function (package) { @@ -122,7 +140,7 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "packageInstallApiBaseUrl", "CleanUp"), package), - 'Failed to create package manifest for zip file '); + 'Failed to install package. Error during the step "CleanUp" '); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js new file mode 100644 index 0000000000..ea40c066f0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js @@ -0,0 +1,119 @@ +/** + * @ngdoc service + * @name umbraco.resources.redirectUrlResource + * @function + * + * @description + * Used by the redirect url dashboard to get urls and send requests to remove redirects. + */ +(function() { + 'use strict'; + + function redirectUrlsResource($http, umbRequestHelper) { + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#searchRedirectUrls + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to search redirects + * ##usage + *
+         * redirectUrlsResource.searchRedirectUrls("", 0, 20)
+         *    .then(function(response) {
+         *
+         *    });
+         * 
+ * @param {String} searchTerm Searh term + * @param {Int} pageIndex index of the page to retrive items from + * @param {Int} pageSize The number of items on a page + */ + function searchRedirectUrls(searchTerm, pageIndex, pageSize) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "SearchRedirectUrls", + { searchTerm: searchTerm, page: pageIndex, pageSize: pageSize })), + 'Failed to retrieve data for searching redirect urls'); + } + + function getEnableState() { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "GetEnableState")), + 'Failed to retrieve data to check if the 301 redirect is enabled'); + } + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#deleteRedirectUrl + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to delete a redirect + * ##usage + *
+         * redirectUrlsResource.deleteRedirectUrl(1234)
+         *    .then(function() {
+         *
+         *    });
+         * 
+ * @param {Int} id Id of the redirect + */ + function deleteRedirectUrl(id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "DeleteRedirectUrl", { id: id })), + 'Failed to remove redirect'); + } + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#toggleUrlTracker + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to enable or disable redirect url tracker + * ##usage + *
+         * redirectUrlsResource.toggleUrlTracker(true)
+         *    .then(function() {
+         *
+         *    });
+         * 
+ * @param {Bool} disable true/false to disable/enable the url tracker + */ + function toggleUrlTracker(disable) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "ToggleUrlTracker", { disable: disable })), + 'Failed to toggle redirect url tracker'); + } + + var resource = { + searchRedirectUrls: searchRedirectUrls, + deleteRedirectUrl: deleteRedirectUrl, + toggleUrlTracker: toggleUrlTracker, + getEnableState: getEnableState + }; + + return resource; + + } + + angular.module('umbraco.resources').factory('redirectUrlsResource', redirectUrlsResource); + +})(); 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/listviewprevaluehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js new file mode 100644 index 0000000000..70bba2d26a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js @@ -0,0 +1,61 @@ +/** + @ngdoc service + * @name umbraco.services.listViewPrevalueHelper + * + * + * @description + * Service for accessing the prevalues of a list view being edited in the inline list view editor in the doctype editor + */ +(function () { + 'use strict'; + + function listViewPrevalueHelper() { + + var prevalues = []; + + /** + * @ngdoc method + * @name umbraco.services.listViewPrevalueHelper#getPrevalues + * @methodOf umbraco.services.listViewPrevalueHelper + * + * @description + * Set the collection of prevalues + */ + + function getPrevalues() { + return prevalues; + } + + /** + * @ngdoc method + * @name umbraco.services.listViewPrevalueHelper#setPrevalues + * @methodOf umbraco.services.listViewPrevalueHelper + * + * @description + * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage + * + * @param {Array} values Array of prevalues + */ + + function setPrevalues(values) { + prevalues = values; + } + + + + var service = { + + getPrevalues: getPrevalues, + setPrevalues: setPrevalues + + }; + + return service; + + } + + + angular.module('umbraco.services').factory('listViewPrevalueHelper', listViewPrevalueHelper); + + +})(); 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 30815027b0..ba395becfc 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 @@ -58,6 +58,44 @@ function versionHelper() { } 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).utcOffset(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) { return { 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/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js index 386e9a6712..54519f353b 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -100,7 +100,6 @@ function SearchController($scope, searchService, $log, $location, navigationServ //a canceler exists, so perform the cancelation operation and reset if (canceler) { - console.log("CANCELED!"); canceler.resolve(); canceler = $q.defer(); } diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 4b732bf56e..5cb8e6230f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -17,7 +17,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop //add to umbraco installer facts here var facts = ['Umbraco helped millions of people watch a man jump from the edge of space', - 'Over 300 000 websites are currently powered by Umbraco', + 'Over 370 000 websites are currently powered by Umbraco', "At least 2 people have named their cat 'Umbraco'", 'On an average day, more than 1000 people download Umbraco', 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', @@ -56,7 +56,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop return (found) ? found.description : null; } - //calculates the offset of the progressbar on the installaer + //calculates the offset of the progressbar on the installer function calculateProgress(steps, next) { var sorted = _.sortBy(steps, "serverOrder"); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index 78e90445db..0b9e22a0f3 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -12,7 +12,7 @@
  • Loading... - {{pck.name}} + {{pck.name}}
  • 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/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 02f56e4046..9d1ec8cd32 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -118,6 +118,12 @@ body { } } +@media (max-width: 500px) { + #search-form .form-search { + width: ~"(calc(~'100%' - ~'80px'))"; + } +} + #navigation { left: 80px; top: 0; @@ -217,19 +223,17 @@ body { padding-left:20px } - - @media (max-width: 767px) { // make leftcolumn smaller on tablets #leftcolumn { - width: 60px; + width: 61px; } #applications ul.sections { - width: 60px; + width: 61px; } ul.sections.sections-tray { - width: 60px; + width: 61px; } #applications-tray { left: 60px; @@ -241,35 +245,24 @@ body { left: 30px; } #umbracoMainPageBody .umb-modal-left.fade.in { - margin-left: 60px; + margin-left: 61px; } } - - @media (max-width: 500px) { // make leftcolumn smaller on mobiles #leftcolumn { - width: 40px; + width: 41px; } #applications ul.sections { - width: 40px; - } - ul.sections li [class^="icon-"]:before { - font-size: 25px!important; - } - #applications ul.sections li.avatar a img { - width: 25px; - } - ul.sections a span { - display:none !important; + width: 41px; } #applications ul.sections-tray { - width: 40px; + width: 41px; } ul.sections.sections-tray { - width: 40px; + width: 41px; } #applications-tray { left: 40px; @@ -281,7 +274,7 @@ body { left: 20px; } #umbracoMainPageBody .umb-modal-left.fade.in { - margin-left: 40px; + margin-left: 41px; width: 85%!important; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index dfc275c4b0..2b53fbddfc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -60,6 +60,9 @@ @import "application/shadows.less"; @import "application/animations.less"; +// Utilities +@import "utilities/_font-weight.less"; + // Belle styles @import "buttons.less"; @import "forms.less"; @@ -111,6 +114,8 @@ @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"; @@ -118,6 +123,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"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 6d43286f2d..00dd8592ff 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -2,32 +2,42 @@ contains styling for all main editor directives */ -.umb-editor-wrapper{ +.umb-editor-wrapper { background: white; position: absolute; - top: 0px; bottom: 0px; left: 0px; right: 0px; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + + > form { + display: flex; + flex-direction: column; + height: 100%; + } } - -.umb-editor-header{ +.umb-editor-header { background: @grayLighter; border-bottom: 1px solid @grayLight; - position: absolute; - height: 99px; - top: 0px; - right: 0px; - left: 0px; + flex: 0 0 99px; + position: relative; } +.umb-editor-header__actions-menu { + margin-left: auto; +} .umb-editor-container { - top: 101px; - left: 0px; - right: 0px; - bottom: 52px; - position: absolute; - clear: both; + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; overflow: auto; + flex: 1 1 100%; } .umb-editor-wrapper.-no-footer .umb-editor-container { @@ -38,18 +48,12 @@ overflow: hidden; } -.umb-editor-drawer{ +.umb-editor-drawer { margin: 0; padding: 10px 20px; - z-index: 999; - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; - height: 31px; - background: @grayLighter; border-top: 1px solid @grayLight; + flex: 1 0 31px; } 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 8c1acd97d8..0792925571 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,7 +12,7 @@ .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: 101px; /* height of header: 100px + its bottom-border: 1px */ + top: 100px; /* height of header */ margin-top: 0; margin-bottom: 0; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 0af0555b89..7a7ac15c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -133,7 +133,7 @@ bottom: 0; border: none; box-shadow: 0 0 20px rgba(0,0,0,0.19), 0 0 6px rgba(0,0,0,0.23); - margin-left: 80px; + margin-left: 81px; } .umb-overlay.umb-overlay-left .umb-overlay-header { @@ -148,7 +148,14 @@ @media (max-width: 767px) { .umb-overlay.umb-overlay-left { - margin-left: 60px; + margin-left: 61px; + } +} + +@media (max-width: 500px) { + .umb-overlay.umb-overlay-left { + margin-left: 41px; + width: ~"(calc(~'100%' - ~'41px'))"; } } 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-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 5854599722..d09946dfa3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -2,16 +2,22 @@ list-style: none; margin-bottom: 0; margin-left: 0; + display: flex; + flex-wrap: wrap; } .umb-breadcrumbs__ancestor { - display: inline-block; + display: flex; } .umb-breadcrumbs__ancestor-link, .umb-breadcrumbs__ancestor-text { - font-size: 11px; - color: #555; + font-size: 11px; + color: #555; + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .umb-breadcrumbs__ancestor-link { 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 index e8477396bf..9f1ef219a9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less @@ -19,7 +19,7 @@ right: 0; bottom: 0; left: 0; - background: rgba(0, 0, 0, 0.2); + background: rgba(21, 21, 23, 0.7); width: 100%; height: 100%; } @@ -27,7 +27,14 @@ .umb-lightbox__close { position: absolute; top: 20px; - right: 20px; + right: 60px; +} + +.umb-lightbox__close i { + font-size: 20px; + cursor: pointer; + height: 40px; + width: 40px; } .umb-lightbox__images { 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 index a272a40c90..2a624b1c56 100644 --- 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 @@ -1,42 +1,15 @@ -// -// Install local package -// +/* + + Install local package + +*/ // Helpers .faded { color: @grayMed; } -.inline-flex { - display: inline-flex; -} -.mt-3 { - margin-top: 30px; -} - -// Upload state - -.umb-upload-local, -.umb-info-local { - display: flex; - justify-content: center; - align-items: center; - text-align: center; -} - -.umb-upload-local form, -.umb-info-local-items { - display: flex; - flex-direction: column; - - justify-content: center; - align-items: center; - - margin: 0; - - max-width: 640px; -} .umb-upload-local__dropzone { position: relative; @@ -75,9 +48,14 @@ font-weight: bold; color: @blue; cursor: pointer; + + &:hover { + text-decoration: underline; + } } + // Accept terms .umb-accept-terms { display: flex; @@ -104,6 +82,7 @@ } + // Info state .umb-info-local-items { border: 2px solid @grayLight; @@ -116,6 +95,8 @@ align-items: center; margin: 0 20px; + + width: 100%; max-width: 540px; } @@ -127,9 +108,6 @@ } } -.umb-info-local-item { - margin-bottom: 20px; -} .umb-info-local-items .umb-package-icon { width: 100%; @@ -148,6 +126,10 @@ padding: 20px 40px; } -.umb-info-local-items .umb-package-installer-label { - margin-left: 10px; +.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 index 6278e78a6e..f14df918c0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -9,6 +9,14 @@ padding: 20px 60px; } +@media (max-width: 768px) { + + .umb-packages-view-wrapper { + padding: 0; + } + +} + .umb-packages-section { margin-bottom: 40px; } @@ -52,11 +60,55 @@ .umb-package { padding: 10px; box-sizing: border-box; - width: 25%; + 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: flex; + display: block; flex-wrap: wrap; flex-direction: column; justify-content: center; @@ -104,6 +156,7 @@ .umb-package-icon img { max-width: 70px; + width: 70px; height: auto; } @@ -278,6 +331,11 @@ width: 100%; } +.umb-era-button.umb-button--s { + height: 30px; + font-size: 13px; +} + /* CATEGORIES */ @@ -304,6 +362,48 @@ 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; @@ -312,12 +412,14 @@ .umb-packages-category.-first { border-left: 1px solid @grayLight; - border-radius: 3px 0 0 3px; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; } .umb-packages-category.-last { border-right: 1px solid @grayLight; - border-radius: 0 3px 3px 0; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; } /* PACKAGE DETAILS */ @@ -326,23 +428,46 @@ display: flex; } -.umb-package-details__back-link { +a.umb-package-details__back-link { font-weight: bold; - color: @grayMed; + color: @black; } .umb-package-details__back-link:hover { - color: @black; + color: @grayMed; text-decoration: none; } + +@sidebarwidth: 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%' - ~'@{sidebarwidth}' - ~'40px'))"; // Make sure that the main content area doesn't gets affected by inline styling } .umb-package-details__sidebar { - flex: 0 0 350px; + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-package-details { + flex-direction: column; + } + + .umb-package-details__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-package-details__sidebar { + flex: 1 1 auto; + width: 100%; + } } .umb-package-details__section { @@ -367,15 +492,17 @@ } .umb-package-details__information-item { - display: flex; - margin-bottom: 5px; + margin-bottom: 10px; font-size: 13px; } .umb-package-details__information-item-label { color: black; font-weight: bold; - margin-right: 3px; +} + +.umb-package-details__information-item-content { + word-break: break-word; } .umb-package-details__information-item-label-2 { @@ -419,6 +546,7 @@ } .umb-package-details__owner-profile-avatar { margin-right: 15px; + flex: 0 0 auto; } .umb-package-details__owner-profile-name { @@ -440,12 +568,13 @@ } .umb-gallery__thumbnail { - flex: 1 1 100px; + 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 { @@ -453,36 +582,6 @@ border-color: @blue; } -/* Avatar */ - -.umb-avatar { - border-radius: 50%; - width: 50px; -} - -/* Progress bar */ - -.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; -} - /* PACKAGE LIST */ .umb-package-list { 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/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 874f9e9551..bc8e19cf2b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -3,6 +3,8 @@ display: flex; flex-direction: column; + position: relative; + border: 1px solid @grayLight; flex-wrap: nowrap; @@ -11,6 +13,25 @@ min-width: 640px; } +.umb-table.umb-table-inactive { + + &:before { + content: ""; + background: rgba(255, 255, 255, 0.75); + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + z-index: 10; + + outline: 1px solid rgba(255, 255, 255, 0.75); + } + +} + .umb-table a { text-decoration: none; cursor: pointer; @@ -93,6 +114,14 @@ input.umb-table__input { } } +.umb-table-body .umb-table-row.-solid { + cursor: default; + + &:hover { + background-color: white; + } +} + .umb-table-body__link { text-decoration: none; @@ -183,6 +212,8 @@ input.umb-table__input { user-select: none; } + + .umb-table-row.-selected, .umb-table-row.-selected:hover { background-color: fade(@blueDark, 4%); @@ -212,7 +243,7 @@ input.umb-table__input { text-overflow: ellipsis; } -.umb-table-cell:first-of-type { +.umb-table-cell:first-of-type:not(.not-fixed) { flex: 0 0 25px; margin: 0 0 0 15px; 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/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less index a5f2d454f1..96b8a611db 100644 --- a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -1,12 +1,20 @@ -/* Vars */ - @red-orange: #FF3F34; - @sunrise: #F5D226; - @emerald: #50C878; - .umb-healthcheck { display: flex; flex-wrap: wrap; + margin-left: -10px; + margin-right: -10px; +} + +.umb-healthcheck-help-text { + line-height: 1.6em; + margin-bottom: 30px; +} + +.umb-healthcheck-action-bar { + display: flex; + justify-content: flex-end; + margin-bottom: 20px; } @@ -42,7 +50,6 @@ /* Title */ .umb-healthcheck-title { - margin-bottom: 15px; font-size: 14px; font-weight: bold; } @@ -50,28 +57,26 @@ /* Messages */ .umb-healthcheck-messages { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; + margin-top: 15px; } .umb-healthcheck-message { position: relative; background: #fff; border-radius: 50px; - display: flex; - align-items: center; - flex: 1 1 auto; - padding: 5px 10px; + display: inline-flex; + align-items: center; + padding-left: 8px; + padding-right: 8px; margin-bottom: 5px; color: #000; font-weight: bold; + font-size: 13px; } .umb-healthcheck-message i { - font-size: 20px; - margin-right: 5px; + font-size: 15px; + margin-right: 3px; } .umb-healthcheck-details-link { @@ -99,11 +104,11 @@ font-size: 14px; font-weight: bold; - height: 38px; + height: 40px; line-height: 1; max-width: 100%; - padding: 0 18px; + padding: 0 15px; color: #484848; background-color: #e0e0e0; @@ -235,12 +240,13 @@ } .umb-healthcheck-group__details-checks { - border: 2px solid @grayLight; + border: 1px solid @grayLight; border-top: none; border-radius: 0 0 3px 3px; } .umb-healthcheck-group__details-check { + position: relative; } .umb-healthcheck-group__details-check-title { @@ -252,20 +258,19 @@ font-size: 14px; color: @black; font-weight: bold; + margin-bottom: 2px; } .umb-healthcheck-group__details-check-description { font-size: 12px; color: @grayMed; - line-height: 1.6rem; - //margin-top: 10px; + line-height: 1.6em; } .umb-healthcheck-group__details-status { padding: 15px 0; display: flex; border-bottom: 2px solid @grayLighter; - position: relative; } .umb-healthcheck-group__details-status-overlay { @@ -328,6 +333,6 @@ } .umb-healthcheck-group__details-status-action-description { - margin-top: 10px; - font-size: 13px; + margin-top: 5px; + font-size: 11px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 88762edf0c..95c8a49b36 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -166,51 +166,6 @@ background: none } -.umb-listview .pagination { - margin: 0; -} - -.umb-listview .table th { - font-weight: normal -} - -.umb-listview .showing { - padding: 8px 4px 2px 4px; - background: none; - font-size: 11px; - color: #b0b0b0 -} - -.umb-listview .pagination { - text-align: center; -} - -.umb-listview .pagination ul { - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - border-radius: 0px; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0); - background: none -} - -.umb-listview .pagination ul > li > a, .pagination ul > li > span { - border:none; - padding: 8px 4px 2px 4px; - background: none; - font-size: 12px; - color: #b0b0b0 -} - -.umb-listview .pagination ul > li.active > a, .umb-listview .pagination ul > li > a:hover { - color: @black; -} - -.umb-listview .pagination ul > li.disabled > a, .umb-listview .pagination ul > li.disabled > a:hover { - color: @grayLight; -} - /* TEMP */ .umb-listview .table-striped tbody td { diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 0c2c2f07ad..8ac5af175c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -114,7 +114,7 @@ .umbracoDialog{ width: auto !Important; height: auto !Important; - padding: 20px; + padding: 20px; } .umbracoDialog .umb-pane{margin-left: 0px; margin-right: 0px; margin-top: 0px;} .umbracoDialog .umb-dialog-body .umb-pane{margin-left: 20px; margin-right: 20px; margin-top: 20px;} @@ -125,7 +125,11 @@ .umb-modal .controls-row{margin-left: 0px !important;} /* modal and umb-modal are used for right.hand dialogs */ -.modal.fade.in{border: none !important; border-radius: none !important;} +.modal { + border-radius: 0 !important; + + &.fade.in{border: none !important;} +} .umb-modal.fade { outline: none; top: 0 !important; diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 83b61e48a5..c80f72921c 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, @@ -483,13 +483,15 @@ input.umb-panel-header-description { .umb-editor-drawer-content { display: flex; align-items: center; - //justify-content: space-between; } .umb-editor-drawer-content__right-side { margin-left: auto; + flex: 0 0 auto; + padding-left: 10px; } .umb-editor-drawer-content__left-side { margin-right: auto; + padding-right: 10px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 85a1d9e36c..16d48807eb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -704,6 +704,31 @@ ul.color-picker li a { padding-top: 27px; } +.umb-fileupload .file-icon { + text-align: center; + display: block; + position: relative; + padding: 5px 0; + + > .icon { + font-size: 70px; + line-height: 110%; + color: #666; + text-align: center; + } + + > span { + color: #fff; + background: #666; + padding: 1px 3px; + font-size: 12px; + line-height: 130%; + position: absolute; + top: 45px; + left: 110px; + } +} + .umb-fileupload input { font-size: 12px; line-height: 1; diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index c89be83a10..c764280c8d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -18,8 +18,8 @@ ul.sections li { transition: all .3s linear; } -ul.sections li [class^="icon-"]:before, -ul.sections li [class*=" icon-"]:before, +ul.sections li [class^="icon-"], +ul.sections li [class*=" icon-"], ul.sections li img.icon-section { font-size: 30px; line-height: 20px; /* set line-height to ensure all icons use same line-height */ @@ -31,8 +31,8 @@ ul.sections li img.icon-section { transition: all .3s linear; } -ul.sections:hover li [class^="icon-"]:before, -ul.sections:hover li [class*=" icon-"]:before, +ul.sections:hover li [class^="icon-"], +ul.sections:hover li [class*=" icon-"], ul.sections:hover li img.icon-section { opacity: 1 } @@ -111,32 +111,43 @@ ul.sections li.help { bottom: 0; left: 0; display: block; - width: 100%; + width: ~"(calc(~'100%' - ~'5px'))"; //subtract 4px orange border + 1px border-right for sections } ul.sections li.help a { border-bottom: none; } +@media (max-width: 500px) { + ul.sections li [class^="icon-"], + ul.sections li [class*=" icon-"] { + font-size: 25px; + } + ul.sections li:not(.avatar) a { + padding-top: 12px; + padding-bottom: 6px; + + .icon, .icon-section { + display: inline-block; + padding-left: 2px; + } + } + ul.sections a span { + display:none; + } +} + // Section slide-out tray for additional apps // ------------------------- -li.expand a, li.expand{border: none !Important;} +li.expand a, li.expand{border: none !important;} li.expand { + > a > i.icon { + transition: all .3s linear; + } &.open > a > i.icon { - -webkit-transition: all 1s !important; - -o-transition: all 1s !important; - -moz-transition: all 1s !important; - transition: all 1s !important; - - &:before { - -ms-transform: rotate(180deg) !important; /* IE 9 */ - -webkit-transform: rotate(180deg) !important; /* Chrome, Safari, Opera */ - -o-transform: rotate(180deg) !important; - -moz-transform: rotate(180deg) !important; - transform: rotate(180deg) !important; - } + transform: rotate(180deg); } } 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..c0815fa8ac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_flexbox.less @@ -0,0 +1,102 @@ +/* + + Flexbox + +*/ + +.flex { display: flex; } +.flex-inline { display: inline-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; } + +.flx-i { + flex: 1; +} + + +.flx-g0 { + flex-grow: 0; +} +.flx-g1 { + flex-grow: 1; +} + +.flx-s0 { + flex-shrink: 0; +} +.flx-s1 { + flex-shrink: 1; +} + + +.flx-b0 { + flex-basis: 0%; +} +.flx-b1 { + flex-basis: 10%; +} +.flx-b2 { + flex-basis: 20%; +} +.flx-b3 { + flex-basis: 30%; +} +.flx-b4 { + flex-basis: 40%; +} +.flx-b5 { + flex-basis: 50%; +} +.flx-b6 { + flex-basis: 60%; +} +.flx-b7 { + flex-basis: 70%; +} +.flx-b8 { + flex-basis: 80%; +} +.flx-b9 { + flex-basis: 90%; +} +.flx-b10 { + flex-basis: 100%; +} +.flx-ba { + flex-basis: auto; +} + +/* 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/_font-weight.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less new file mode 100644 index 0000000000..111ed4b2ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less @@ -0,0 +1,10 @@ +/* + FONT WEIGHT +*/ + + + +.light { font-weight: 300; } +.normal { font-weight: 500; } +.semi-bold { font-weight: 600; } +.bold { font-weight: 700; } 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/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index c38c55fcfb..aa037b7431 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -79,7 +79,7 @@ } $scope.loginSubmit = function (login, password) { - + //if the login and password are not empty we need to automatically // validate them - this is because if there are validation errors on the server // then the user has to change both username & password to resubmit which isn't ideal, @@ -121,13 +121,18 @@ $scope.requestPasswordResetSubmit = function (email) { - $scope.errorMsg = ""; + if (email && email.length > 0) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + $scope.showEmailResetConfirmation = false; if ($scope.requestPasswordResetForm.$invalid) { return; } + $scope.errorMsg = ""; + authResource.performRequestPasswordReset(email) .then(function () { //remove the email entered diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 12f25f42a5..5933848c36 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -25,7 +25,7 @@ id="{{login.authType}}" name="provider" value="{{login.authType}}" title="Log in using your {{login.caption}} account"> - Sign in with {{login.caption}} + Sign in with {{login.caption}} @@ -33,7 +33,7 @@

    -
    Or
    +
    or
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js index f2052ccc65..0c1cb5b62e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js @@ -32,7 +32,7 @@ break; case 1: //error - $scope.form.info = "Computer says no"; + $scope.form.info = "Could not embed media - please ensure the URL is valid"; break; case 2: $scope.form.preview = data.Markup; @@ -44,7 +44,7 @@ .error(function () { $scope.form.supportsDimensions = false; $scope.form.preview = ""; - $scope.form.info = "Computer says no"; + $scope.form.info = "Could not embed media - please ensure the URL is valid"; }); } else { $scope.form.supportsDimensions = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html index 83be1a7782..2b2d3bd1cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html @@ -46,18 +46,18 @@
    -
    + + + -
    + + Link your {{login.caption}} account + + - + + Link your {{login.caption}} account + + - + +
    @@ -167,7 +167,7 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js index 4a19ea9926..5f6bb23001 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js @@ -1,4 +1,4 @@ -function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { +function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { $scope.indexerDetails = []; $scope.searcherDetails = []; @@ -6,7 +6,9 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo function checkProcessing(indexer, checkActionName) { umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", checkActionName, { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + checkActionName, + { indexerName: indexer.name })), 'Failed to check index processing') .then(function(data) { @@ -17,70 +19,73 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo indexer[k] = data[k]; } indexer.isProcessing = false; - } - else { - $timeout(function () { - //don't continue if we've tried 100 times - if (indexer.processingAttempts < 100) { - checkProcessing(indexer, checkActionName); - //add an attempt - indexer.processingAttempts++; - } - else { - //we've exceeded 100 attempts, stop processing - indexer.isProcessing = false; - } - }, 1000); + } else { + $timeout(function() { + //don't continue if we've tried 100 times + if (indexer.processingAttempts < 100) { + checkProcessing(indexer, checkActionName); + //add an attempt + indexer.processingAttempts++; + } else { + //we've exceeded 100 attempts, stop processing + indexer.isProcessing = false; + } + }, + 1000); } }); } - $scope.search = function (searcher, e) { + $scope.search = function(searcher, e) { if (e && e.keyCode !== 13) { return; } umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearchResults", { - searcherName: searcher.name, - query: encodeURIComponent(searcher.searchText), - queryType: searcher.searchType - })), + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "GetSearchResults", + { + searcherName: searcher.name, + query: encodeURIComponent(searcher.searchText), + queryType: searcher.searchType + })), 'Failed to search') .then(function(searchResults) { searcher.isSearching = true; searcher.searchResults = searchResults; }); } - + $scope.toggle = function(provider, propName) { if (provider[propName] !== undefined) { provider[propName] = !provider[propName]; - } - else { + } else { provider[propName] = true; } } $scope.rebuildIndex = function(indexer) { if (confirm("This will cause the index to be rebuilt. " + - "Depending on how much content there is in your site this could take a while. " + - "It is not recommended to rebuild an index during times of high website traffic " + - "or when editors are editing content.")) { + "Depending on how much content there is in your site this could take a while. " + + "It is not recommended to rebuild an index during times of high website traffic " + + "or when editors are editing content.")) { indexer.isProcessing = true; indexer.processingAttempts = 0; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostRebuildIndex", { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "PostRebuildIndex", + { indexerName: indexer.name })), 'Failed to rebuild index') - .then(function () { + .then(function() { //rebuilding has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function () { - checkProcessing(indexer, "PostCheckRebuildIndex"); - }, 1000); + $timeout(function() { + checkProcessing(indexer, "PostCheckRebuildIndex"); + }, + 1000); }); } @@ -88,20 +93,23 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo $scope.optimizeIndex = function(indexer) { if (confirm("This will cause the index to be optimized which will improve its performance. " + - "It is not recommended to optimize an index during times of high website traffic " + - "or when editors are editing content.")) { + "It is not recommended to optimize an index during times of high website traffic " + + "or when editors are editing content.")) { indexer.isProcessing = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostOptimizeIndex", { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "PostOptimizeIndex", + { indexerName: indexer.name })), 'Failed to optimize index') - .then(function () { + .then(function() { //optimizing has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function () { - checkProcessing(indexer, "PostCheckOptimizeIndex"); - }, 1000); + $timeout(function() { + checkProcessing(indexer, "PostCheckOptimizeIndex"); + }, + 1000); }); } @@ -111,36 +119,34 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo searcher.isSearching = true; } - //go get the data //combine two promises and execute when they are both done $q.all([ - //get the indexer details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")), - 'Failed to retrieve indexer details') - .then(function(data) { - $scope.indexerDetails = data; - }), - - //get the searcher details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")), - 'Failed to retrieve searcher details') - .then(function(data) { - $scope.searcherDetails = data; - for (var s in $scope.searcherDetails) { - $scope.searcherDetails[s].searchType = "text"; - } - }) - - ]).then(function () { - //all init loading is complete - $scope.loading = false; - }); - + //get the indexer details + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")), + 'Failed to retrieve indexer details') + .then(function(data) { + $scope.indexerDetails = data; + }), + //get the searcher details + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")), + 'Failed to retrieve searcher details') + .then(function(data) { + $scope.searcherDetails = data; + for (var s in $scope.searcherDetails) { + $scope.searcherDetails[s].searchType = "text"; + } + }) + ]) + .then(function() { + //all init loading is complete + $scope.loading = false; + }); } -angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", examineMgmtController); \ No newline at end of file + +angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", ExamineMgmtController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js index e335ddd365..8631b09a45 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js @@ -1,8 +1,7 @@ -(function () { +(function() { "use strict"; function HealthCheckController($scope, healthCheckResource) { - var SUCCESS = 0; var WARNING = 1; var ERROR = 2; @@ -11,57 +10,49 @@ var vm = this; vm.viewState = "list"; - vm.groups = []; + vm.groups = []; vm.selectedGroup = {}; - vm.getStatus = getStatus; - vm.executeAction = executeAction; - vm.checkAllInGroup = checkAllInGroup; + vm.getStatus = getStatus; + vm.executeAction = executeAction; + vm.checkAllGroups = checkAllGroups; + vm.checkAllInGroup = checkAllInGroup; vm.openGroup = openGroup; vm.setViewState = setViewState; - // Get a (grouped) list of all health checks - healthCheckResource.getAllChecks().then( - function(response) { - - // set number of checks which has been executed - for (var i = 0; i < response.length; i++) { - var group = response[i]; - group.checkCounter = 0; - checkAllInGroup(group, group.checks); - } - - vm.groups = response; - - } - ); + // Get a (grouped) list of all health checks + healthCheckResource.getAllChecks() + .then(function(response) { + vm.groups = response; + }); function setGroupGlobalResultType(group) { - var totalSuccess = 0; var totalError = 0; var totalWarning = 0; var totalInfo = 0; // count total number of statusses - angular.forEach(group.checks, function(check){ - angular.forEach(check.status, function(status){ - switch(status.resultType) { - case SUCCESS: - totalSuccess = totalSuccess + 1; - break; - case WARNING: - totalWarning = totalWarning + 1; - break; - case ERROR: - totalError = totalError + 1; - break; - case INFO: - totalInfo = totalInfo + 1; - break; - } + angular.forEach(group.checks, + function(check) { + angular.forEach(check.status, + function(status) { + switch (status.resultType) { + case SUCCESS: + totalSuccess = totalSuccess + 1; + break; + case WARNING: + totalWarning = totalWarning + 1; + break; + case ERROR: + totalError = totalError + 1; + break; + case INFO: + totalInfo = totalInfo + 1; + break; + } + }); }); - }); group.totalSuccess = totalSuccess; group.totalError = totalError; @@ -70,46 +61,58 @@ } - // Get the status of an individual check - function getStatus(check) { - check.loading = true; - check.status = null; - healthCheckResource.getStatus(check.id).then(function(response) { - check.loading = false; - check.status = response; - }); - } + // Get the status of an individual check + function getStatus(check) { + check.loading = true; + check.status = null; + healthCheckResource.getStatus(check.id) + .then(function(response) { + check.loading = false; + check.status = response; + }); + } - function executeAction(check, index, action) { - healthCheckResource.executeAction(action).then(function (response) { - check.status[index] = response; - }); - } + function executeAction(check, index, action) { + check.loading = true; + healthCheckResource.executeAction(action) + .then(function(response) { + check.status[index] = response; + check.loading = false; + }); + } - function checkAllInGroup(group, checks) { + function checkAllGroups(groups) { + // set number of checks which has been executed + for (var i = 0; i < groups.length; i++) { + var group = groups[i]; + checkAllInGroup(group, group.checks); + } + vm.groups = groups; + } + function checkAllInGroup(group, checks) { group.checkCounter = 0; group.loading = true; - angular.forEach(checks, function(check) { + angular.forEach(checks, + function(check) { - check.loading = true; + check.loading = true; - healthCheckResource.getStatus(check.id).then(function(response) { - check.status = response; - group.checkCounter = group.checkCounter + 1; - check.loading = false; + healthCheckResource.getStatus(check.id) + .then(function(response) { + check.status = response; + group.checkCounter = group.checkCounter + 1; + check.loading = false; - // when all checks are done, set global group result - if (group.checkCounter === checks.length) { - setGroupGlobalResultType(group); - group.loading = false; - } - - }); - }); - - } + // when all checks are done, set global group result + if (group.checkCounter === checks.length) { + setGroupGlobalResultType(group); + group.loading = false; + } + }); + }); + } function openGroup(group) { vm.selectedGroup = group; @@ -119,17 +122,14 @@ function setViewState(state) { vm.viewState = state; - if(state === 'list') { + if (state === 'list') { for (var i = 0; i < vm.groups.length; i++) { var group = vm.groups[i]; setGroupGlobalResultType(group); } - } - } - } angular.module("umbraco").controller("Umbraco.Dashboard.HealthCheckController", HealthCheckController); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index 2c60fa6868..ca796043e6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -1,43 +1,55 @@
    -

    Health Check

    -

    The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button.
    - You can add your own health checks, have a look at the documentation for more information about custom health checks.

    -
    +
    -
    -
    -
    {{group.name}}
    +

    Health Check

    +
    +

    The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. + You can add your own health checks, have a look at the documentation for more information about custom health checks.

    +
    -
    - -
    +
    + +
    -
    +
    -
    - - {{ group.totalSuccess }} +
    +
    + +
    {{group.name}}
    + +
    +
    -
    - - {{ group.totalWarning }} -
    +
    -
    - - {{ group.totalError }} -
    +
    + + {{ group.totalSuccess }} +
    + +
    + + {{ group.totalWarning }} +
    + +
    + + {{ group.totalError }} +
    + +
    + + {{ group.totalInfo }} +
    -
    - - {{ group.totalInfo }}
    -
    +
    @@ -46,7 +58,7 @@ - ← Take me back + ← Back to overview @@ -55,7 +67,7 @@
    {{ vm.selectedGroup.name }}
    - +
    @@ -70,10 +82,10 @@
    - - - - + + + +
    @@ -83,24 +95,37 @@
    -
    +
    - - Set new value: - - - + + +
    +
    + +
    + + + +
    +
    -
    -
    - -
    +
    +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js new file mode 100644 index 0000000000..7acab72455 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js @@ -0,0 +1,162 @@ +(function() { + "use strict"; + + function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, localizationService, $q) { + //...todo + //search by url or url part + //search by domain + //display domain in dashboard results? + + //used to cancel any request in progress if another one needs to take it's place + var vm = this; + var canceler = null; + + vm.dashboard = { + searchTerm: "", + loading: false, + urlTrackerDisabled: false, + userIsAdmin: false + }; + + vm.pagination = { + pageIndex: 0, + pageNumber: 1, + totalPages: 1, + pageSize: 20 + }; + + vm.goToPage = goToPage; + vm.search = search; + vm.removeRedirect = removeRedirect; + vm.disableUrlTracker = disableUrlTracker; + vm.enableUrlTracker = enableUrlTracker; + vm.filter = filter; + vm.checkEnabled = checkEnabled; + + function activate() { + vm.checkEnabled().then(function() { + vm.search(); + }); + } + + function checkEnabled() { + vm.dashboard.loading = true; + return redirectUrlsResource.getEnableState().then(function (response) { + vm.dashboard.urlTrackerDisabled = response.enabled !== true; + vm.dashboard.userIsAdmin = response.userIsAdmin; + vm.dashboard.loading = false; + }); + } + + function goToPage(pageNumber) { + vm.pagination.pageIndex = pageNumber - 1; + vm.pagination.pageNumber = pageNumber; + vm.search(); + } + + function search() { + + vm.dashboard.loading = true; + + var searchTerm = vm.dashboard.searchTerm; + if (searchTerm === undefined) { + searchTerm = ""; + } + + redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function(response) { + + vm.redirectUrls = response.searchResults; + + // update pagination + vm.pagination.pageIndex = response.currentPage; + vm.pagination.pageNumber = response.currentPage + 1; + vm.pagination.totalPages = response.pageCount; + + vm.dashboard.loading = false; + + }); + } + + function removeRedirect(redirectToDelete) { + localizationService.localize("redirectUrls_confirmRemove", [redirectToDelete.originalUrl, redirectToDelete.destinationUrl]).then(function (value) { + var toggleConfirm = confirm(value); + + if (toggleConfirm) { + redirectUrlsResource.deleteRedirectUrl(redirectToDelete.redirectId).then(function () { + + var index = vm.redirectUrls.indexOf(redirectToDelete); + vm.redirectUrls.splice(index, 1); + notificationsService.success(localizationService.localize("redirectUrls_redirectRemoved")); + + // check if new redirects needs to be loaded + if (vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) { + + // if we are not on the first page - get records from the previous + if (vm.pagination.pageIndex > 0) { + vm.pagination.pageIndex = vm.pagination.pageIndex - 1; + vm.pagination.pageNumber = vm.pagination.pageNumber - 1; + } + + search(); + } + }, function (error) { + notificationsService.error(localizationService.localize("redirectUrls_redirectRemoveError")); + }); + } + }); + } + + function disableUrlTracker() { + localizationService.localize("redirectUrls_confirmDisable").then(function(value) { + var toggleConfirm = confirm(value); + if (toggleConfirm) { + + redirectUrlsResource.toggleUrlTracker(true).then(function () { + activate(); + notificationsService.success(localizationService.localize("redirectUrls_disabledConfirm")); + }, function (error) { + notificationsService.warning(localizationService.localize("redirectUrls_disableError")); + }); + + } + }); + } + + function enableUrlTracker() { + redirectUrlsResource.toggleUrlTracker(false).then(function() { + activate(); + notificationsService.success(localizationService.localize("redirectUrls_enabledConfirm")); + }, function(error) { + notificationsService.warning(localizationService.localize("redirectUrls_enableError")); + }); + } + + var filterDebounced = _.debounce(function(e) { + + $scope.$apply(function() { + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } else { + canceler = $q.defer(); + } + + vm.search(); + + }); + + }, 200); + + function filter() { + vm.dashboard.loading = true; + filterDebounced(); + } + + activate(); + + } + + angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", RedirectUrlsController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html new file mode 100644 index 0000000000..c63a444839 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -0,0 +1,113 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    +
    Original URL
    +
    +
    Redirected To
    +
    +
    + +
    + +
    + + + + +
    + +
    + +
    + + + +
    + +
    + +
    + +
    + + +
    No redirects have been made
    + When a published page gets renamed or moved a redirect will automatically be made to the new page +
    + + + + + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js index 09b638b05f..bb8696aca0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js @@ -1,4 +1,4 @@ -function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, $q, $timeout) { +function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) { function check(item) { var action = item.check; @@ -20,12 +20,12 @@ function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, "or when editors are editing content.")) { item.fixing = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), - 'Failed to retrieve data integrity status') - .then(function (result) { - item.fixing = false; - item.invalid = result === "false"; - }); + $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), + 'Failed to retrieve data integrity status') + .then(function(result) { + item.fixing = false; + item.invalid = result === "false"; + }); } } } @@ -57,6 +57,6 @@ function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, for (var i in $scope.items) { check($scope.items[i]); } - } -angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", xmlDataIntegrityReportController); \ No newline at end of file + +angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", XmlDataIntegrityReportController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html index df235fb254..11f1834ae4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html @@ -26,6 +26,4 @@
    - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index fa9849022c..3a45776872 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -9,6 +9,6 @@
  • Download the Editors Manual for details on working with the Umbraco UI
  • Ask a question in the Community Forum
  • Watch our tutorial videos (some are free, some require a subscription)
  • -
  • Find out about our productivity boosting tools and commercial support
  • +
  • Find out about our productivity boosting tools and commercial support
  • Find out about real-life training and certification opportunities
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html b/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html index 5a05e9ca85..4558a21e2a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html @@ -1,8 +1,19 @@
    - - + +
    +
    +
    + +

    + When items are deleted from the recycle bin, they will be gone forever. + Are you sure? +

    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js index 99f40d14bb..a8be3d0be5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js @@ -10,8 +10,12 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer $scope.performDelete = function() { + // stop from firing again on double-click + if ($scope.busy) { return false; } + //mark it for deletion (used in the UI) $scope.currentNode.loading = true; + $scope.busy = true; mediaResource.deleteById($scope.currentNode.id).then(function () { $scope.currentNode.loading = false; @@ -45,6 +49,7 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer }, function (err) { $scope.currentNode.loading = false; + $scope.busy = false; //check if response is ysod if (err.status && err.status >= 500) { diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js index dd6ceb369e..218654b464 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js @@ -6,18 +6,33 @@ * @description * The controller for deleting media */ -function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService) { +function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) { + + $scope.busy = false; $scope.performDelete = function() { //(used in the UI) + $scope.busy = true; $scope.currentNode.loading = true; - mediaResource.emptyRecycleBin($scope.currentNode.id).then(function () { + mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { + + $scope.busy = false; $scope.currentNode.loading = false; - //TODO: Need to sync tree, etc... + + //show any notifications + if (angular.isArray(result.notifications)) { + for (var i = 0; i < result.notifications.length; i++) { + notificationsService.showNotification(result.notifications[i]); + } + } + treeService.removeChildNodes($scope.currentNode); navigationService.hideMenu(); + + //reload the current view + $route.reload(); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/category.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/category.controller.js deleted file mode 100644 index 61332e850f..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/packager/category.controller.js +++ /dev/null @@ -1,100 +0,0 @@ -(function () { - "use strict"; - - function PackagesCategoryController($scope, $routeParams) { - - var vm = this; - - vm.page = {}; - vm.page.name = "Category"; - - vm.selectCategory = selectCategory; - - vm.categories = [ - { - "icon": "icon-male-and-female", - "name": "All", - "active": false - }, - { - "icon": "icon-male-and-female", - "name": "Collaboration", - "active": true - }, - { - "icon": "icon-molecular-network", - "name": "Backoffice extensions" - }, - { - "icon": "icon-brackets", - "name": "Developer tools" - }, - { - "icon": "icon-wand", - "name": "Starter kits" - }, - { - "icon": "icon-medal", - "name": "Umbraco Pro" - }, - { - "icon": "icon-wrench", - "name": "Website utilities" - } - ]; - - vm.packages = [ - { - "name": "uSightly", - "description": "An HTML5 audio player based on jPlayer", - "karma": "1", - "downloads": "1672", - "icon":"https://our.umbraco.org/media/wiki/150283/635768313097111400_usightlylogopng.png?bgcolor=fff&height=154&width=281&format=png" - }, - { - "name": "Kill IE6", - "description": "A simple port of the IE6 warning script (http://code.google.com/p/ie6-upgrade-warning/) to use in your Umbraco websites.", - "karma": "11", - "downloads": "688", - "icon":"https://our.umbraco.org/media/wiki/9138/634697622367666000_offroadcode-100x100.png?bgcolor=fff&height=154&width=281&format=png" - }, - { - "name": "Examine Media Indexer", - "description": "CogUmbracoExamineMediaIndexer", - "karma": "3", - "downloads": "1329", - "icon":"https://our.umbraco.org/media/wiki/50703/634782902373558000_cogworks.jpg?bgcolor=fff&height=154&width=281&format=png" - }, - { - "name": "SVG Icon Picker", - "description": "A picker, for picking icons from an SVG spritesheet.", - "karma": "5", - "downloads": "8", - "icon":"https://our.umbraco.org/media/wiki/154472/635997115126742822_logopng.png?bgcolor=fff&height=154&width=281&format=png" - }, - { - "name": "Pipeline CRM", - "description": "Pipeline is a social CRM that lives in Umbraco back-office. It tracks opportunities and helps teams collaborate with timelines and tasks. It stores information about your customers and your interactions with them. It integrates with your website, capturing opportunities from forms and powering personal portals.", - "karma": "3", - "downloads": "105", - "icon":"https://our.umbraco.org/media/wiki/152476/635917291068518788_pipeline-crm-logopng.png?bgcolor=fff&height=154&width=281&format=png" - }, - { - "name": "CodeMirror", - "description": "CodeMirror Editor for Umbraco", - "karma": "1", - "downloads": "70", - "icon":"https://our.umbraco.org/media/wiki/151028/635810233171153461_logopng.png?bgcolor=fff&height=154&width=281&format=png" - } - ]; - - function selectCategory(category) { - - } - - - } - - angular.module("umbraco").controller("Umbraco.Editors.Packages.CategoryController", PackagesCategoryController); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/category.html b/src/Umbraco.Web.UI.Client/src/views/packager/category.html deleted file mode 100644 index d408b21cd9..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/packager/category.html +++ /dev/null @@ -1,109 +0,0 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/delete.controller.js new file mode 100644 index 0000000000..134336afce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/delete.controller.js @@ -0,0 +1,32 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.Packages.DeleteController + * @function + * + * @description + * The controller for deleting content + */ +function PackageDeleteController($scope, packageResource, treeService, navigationService) { + + $scope.performDelete = function() { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + packageResource.deleteCreatedPackage($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.cancel = function() { + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.Packages.DeleteController", PackageDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/delete.html b/src/Umbraco.Web.UI.Client/src/views/packager/delete.html new file mode 100644 index 0000000000..1a802ab657 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/delete.html @@ -0,0 +1,13 @@ +
    +
    + +

    + Are you sure you want to delete {{currentNode.name}} ? +

    + + + + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/details.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/details.controller.js deleted file mode 100644 index 7f4a48ebb9..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/packager/details.controller.js +++ /dev/null @@ -1,100 +0,0 @@ -(function () { - "use strict"; - - function PackageDetailsController($scope, $routeParams) { - - var vm = this; - - vm.page = {}; - - vm.package = { - "name": "Merchello", - "description": "<p>Merchello is a high performance, designer friendly, open source Umbraco ecommerce package built for the store owner.</p> <p><strong>What Merchello does for you</strong></p> <p>In version 1, Merchello supports a large variety of products with options that can be attached to a single warehouse, processes orders, manages taxes and shipping, and sends out email notifications to your customers. The beauty of Merchello is that while it oversees all of your products, orders, and store settings, it allows Umbraco to maintain your content. This seamless integration gives you the flexibility to build your store in any way imagineable on a robust platform capable of handling a wide variety of store sizes.</p> <p><strong>Find out more on our website</strong></p> <p><strong><a href="https://merchello.com">https://merchello.com</a></strong></p> <p><strong>Contribute</strong></p> <p>We would love and need your help. If you want to contribute to Merchello's core, the easiest way to get started is to fork the project on https://github.com/merchello/Merchello and open src/Merchello.sln in Visual Studio. We're excited to see what you do!</p> <p><strong>Starter Kit</strong></p> <p>We have built a simple starter kit for Merchello called Bazaar, and you can download it below in the package files tab.</p>", - "compatibility": [ - { - "version": "7.4.x", - "percentage": "100" - }, - { - "version": "7.3.x", - "percentage": "86" - }, - { - "version": "7.2.x", - "percentage": "93" - }, - { - "version": "7.1.x", - "percentage": "100" - } - ], - "information": { - "owner": "Rusty Swayne", - "ownerAvatar": "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop", - "ownerKarma": "2673", - "contributors": [ - { - "name": "Lee" - }, - { - "name": "Jason Prothero" - } - ], - "created": "18/12/2013", - "currentVersion": "2.0.0", - "netVersion": "4.5", - "license": "MIT", - "downloads": "4198", - "karma": "53" - }, - "externalSources": [ - { - "name": "Source code", - "url": "https://github.com/merchello/Merchello" - }, - { - "name": "Issue tracker", - "url": "http://issues.merchello.com/youtrack/oauth?state=%2Fyoutrack%2FrootGo" - } - ], - "images": [ - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - { - "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - } - ] - }; - - } - - angular.module("umbraco").controller("Umbraco.Editors.Packages.DetailsController", PackageDetailsController); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/details.html b/src/Umbraco.Web.UI.Client/src/views/packager/details.html deleted file mode 100644 index fa69f55407..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/packager/details.html +++ /dev/null @@ -1,141 +0,0 @@ -
    - - - -
    - - - - - - - - -
    - -
    - -
    - - - -
    - -
    - -
    - -
    - -
    -
    -
    - -
    -
    -
    {{ vm.package.information.owner }}
    -
    - {{ vm.package.information.owner }} has {{ vm.package.information.ownerKarma }} karma points -
    -
    -
    -
    - -
    -
    Information
    -
    - -
    -
    Owner:
    -
    {{vm.package.information.owner}}
    -
    - -
    -
    Contributors:
    - -
    - -
    -
    Created:
    -
    {{vm.package.information.created}}
    -
    - -
    -
    Current version:
    -
    {{vm.package.information.created}}
    -
    - -
    -
    .Net Version:
    -
    {{vm.package.information.netVersion}}
    -
    - -
    -
    License:
    -
    {{vm.package.information.license}}
    -
    - -
    -
    Downloads:
    -
    {{vm.package.information.downloads}}
    -
    - -
    -
    Karma:
    -
    {{vm.package.information.karma}}
    -
    - -
    -
    - -
    -
    Compatibility
    -
    This project is compatible with the following versions as reported by community members who have downloaded this package:
    -
    -
    - {{compatibility.version}} - ({{compatibility.percentage}}%) -
    -
    - -
    -
    -
    - -
    -
    External sources
    - - -
    - -
    - -
    - -
    - -
    - -
    - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js index c03b6cd230..369d919b7d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js @@ -1,17 +1,21 @@ (function () { "use strict"; - function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, $cookieStore) { + function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, localStorageService) { //Hack! // if there is a cookie value for packageInstallUri then we need to redirect there, - // the issue is that we still have webforms and we cannot go to a hash location and then window.reload + // the issue is that we still have webforms and we cannot go to a hash location and then window.reload // because it will double load it. // we will refresh and then navigate there. - - var installPackageUri = window.localStorage.getItem("packageInstallUri"); + + var installPackageUri = localStorageService.get("packageInstallUri"); if (installPackageUri) { - window.localStorage.removeItem("packageInstallUri"); + localStorageService.remove("packageInstallUri"); + } + if (installPackageUri && installPackageUri !== "installed") { + //navigate to the custom installer screen, if it is just "installed", then we'll + //show the installed view $location.path(installPackageUri).search(""); } else { @@ -24,17 +28,19 @@ "name": "Packages", "icon": "icon-cloud", "view": "views/packager/views/repo.html", - "active": true + "active": !installPackageUri || installPackageUri === "navigation" }, { "name": "Installed", "icon": "icon-box", - "view": "views/packager/views/installed.html" + "view": "views/packager/views/installed.html", + "active": installPackageUri === "installed" }, { "name": "Install local", "icon": "icon-add", - "view": "views/packager/views/install-local.html" + "view": "views/packager/views/install-local.html", + "active": installPackageUri === "local" } ]; diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index c68b31038f..e29df0b9ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, $cookieStore, $timeout) { + function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService) { var vm = this; vm.state = "upload"; @@ -9,7 +9,8 @@ vm.localPackage = {}; vm.installPackage = installPackage; vm.installState = { - status: "" + status: "", + progress:0 }; vm.zipFile = { uploadStatus: "idle", @@ -18,8 +19,10 @@ }; $scope.handleFiles = function (files, event) { - for (var i = 0; i < files.length; i++) { - upload(files[i]); + if (files) { + for (var i = 0; i < files.length; i++) { + upload(files[i]); + } } }; @@ -50,8 +53,6 @@ // Throw message back to user with the cause of the error vm.zipFile.serverErrorMessage = data.notifications[0].message; - //TODO: Handle the error in UI - } else { // set done status on file @@ -62,29 +63,32 @@ }).error(function (evt, status, headers, config) { - //TODO: Handle the error in UI - // set status done vm.zipFile.uploadStatus = "error"; - //if the service returns a detailed error - if (evt.InnerException) { - vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage; - - //Check if its the common "too large file" exception - if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { - vm.zipFile.serverErrorMessage = "File too large to upload"; - } - - } else if (evt.Message) { - file.serverErrorMessage = evt.Message; - } - // If file not found, server will return a 404 and display this message if (status === 404) { vm.zipFile.serverErrorMessage = "File not found"; } + else if (status == 400) { + //it's a validation error + vm.zipFile.serverErrorMessage = evt.message; + } + else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + vm.zipFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + } }); } @@ -95,28 +99,27 @@ } function installPackage() { - vm.installState.status = "Installing"; + vm.installState.status = localizationService.localize("packager_installStateImporting"); + vm.installState.progress = "0"; - //TODO: If any of these fail, will they keep calling the next one? packageResource - .installFiles(vm.localPackage) + .import(vm.localPackage) .then(function(pack) { - vm.installState.status = "Importing..."; - return packageResource.import(pack); - }, - installError) - .then(function(pack) { - vm.installState.status = "Installing..."; + vm.installState.progress = "25"; + vm.installState.status = localizationService.localize("packager_installStateInstalling"); + vm.installState.progress = "50"; return packageResource.installFiles(pack); }, installError) .then(function(pack) { - vm.installState.status = "Restarting, please wait..."; + vm.installState.status = localizationService.localize("packager_installStateRestarting"); + vm.installState.progress = "75"; return packageResource.installData(pack); }, installError) .then(function(pack) { - vm.installState.status = "All done, your browser will now refresh"; + vm.installState.status = localizationService.localize("packager_installStateComplete"); + vm.installState.progress = "100"; return packageResource.cleanUp(pack); }, installError) @@ -124,20 +127,25 @@ if (result.postInstallationPath) { //Put the redirect Uri in a cookie so we can use after reloading - window.localStorage.setItem("packageInstallUri", result.postInstallationPath); + localStorageService.set("packageInstallUri", result.postInstallationPath); + } + else { + //set to a constant value so it knows to just go to the installed view + localStorageService.set("packageInstallUri", "installed"); } - //reload on next digest (after cookie) - $timeout(function() { - window.location.reload(true); - }); + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); }, installError); } - + function installError() { - //TODO: Need to do something about this? + //This will return a rejection meaning that the promise change above will stop + return $q.reject(); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html index b25facae7f..07fb21d00b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -1,112 +1,137 @@ -
    +
    - -
    -
    +
    - -
    + +
    + + + +
    -
    +
    - - - Drop to upload + + + Drop to upload - -
    - - or click here to choose files + +
    + - or click here to choose files +
    + +
    + {{vm.zipFile.serverErrorMessage}} +
    -
    -

    Upload package

    -

    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam -

    +

    Upload package

    +

    + Install a local package by selecting it from your machine. Only install packages from sources you know and trust. +

    - -
    - - - -
    -
    - -
    -
    - - -
    - - -
    -

    {{ vm.localPackage.name }}

    - - - -
    - Version - {{ vm.localPackage.version }} -
    - - - -
    - Read me -
    - -
    - -
    - - -
    -
    - This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} -
    -
    -

    {{vm.installState.status}}

    -
    - -
    -
    +
    + + + + ← Cancel and upload another package + + + +
    + + +
    +
    + +
    +
    + + +
    + + +
    +

    {{ vm.localPackage.name }}

    + + + +
    + Version + {{ vm.localPackage.version }} +
    + + + +
    + Read me +
    + +
    + +
    + + +
    + +
    + + +
    + +
    + This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} +
    +
    +

    {{vm.installState.status}}

    +
    + +
    +
    + +
    +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js index e2b5fb4de8..ce1d2cca0f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function PackagesInstalledController($scope, $route, $location, packageResource) { + function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) { var vm = this; @@ -28,17 +28,27 @@ } function uninstallPackage(installedPackage) { - vm.installState.status = "Uninstalling package..."; + vm.installState.status = localizationService.localize("packager_installStateUninstalling"); + vm.installState.progress = "0"; + packageResource.uninstall(installedPackage.id) .then(function () { - if (installedPackage.files.length > 0) { - vm.installState.status = "All done, your browser will now refresh"; - var url = window.location.href + "?uninstalled=" + vm.package.packageGuid; - window.location.reload(true); + if (installedPackage.files.length > 0) { + vm.installState.status = localizationService.localize("packager_installStateComplete"); + vm.installState.progress = "100"; + + //set this flag so that on refresh it shows the installed packages list + localStorageService.set("packageInstallUri", "installed"); + + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); + } else { - init(); + init(); } }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html index 403406545c..5da3c33a76 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html @@ -3,31 +3,43 @@
    -
    Installed packages
    +
    -
    +
    Installed packages
    -
    +
    -
    - - -
    +
    -
    -
    {{ installedPackage.name }}
    -
    - {{ installedPackage.version }} | {{ installedPackage.url }}| {{ installedPackage.author }} +
    + + +
    + +
    +
    {{ installedPackage.name }}
    +
    + {{ installedPackage.version }} | {{ installedPackage.url }}| {{ installedPackage.author }} +
    +
    + +
    +
    -
    -
    -
    + + +

    You don’t have any packages installed.

    +

    You don’t have any packages installed. Either install a local package by selecting it from your machine, or browse through available packages using the "Package" icon in the top right of your screen."

    +
    +
    @@ -39,14 +51,14 @@ -
    +
    -
    +
    - - + +
    @@ -65,7 +77,7 @@
    @@ -74,19 +86,27 @@ {{ vm.package.readme }}
    -
    - +
    +
    + +
    + + +
    +

    {{vm.installState.status}}

    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js index a28f1a761b..e4afb661e3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, $cookieStore) { + function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) { var vm = this; @@ -11,11 +11,13 @@ vm.pagination = { pageNumber: 1, totalPages: 10, - pageSize: 8 + pageSize: 24 }; vm.searchQuery = ""; vm.installState = { - status: "" + status: "", + progress: 0, + type: "ok" }; vm.selectCategory = selectCategory; vm.showPackageDetails = showPackageDetails; @@ -27,10 +29,12 @@ vm.downloadPackage = downloadPackage; vm.openLightbox = openLightbox; vm.closeLightbox = closeLightbox; + vm.search = search; + var currSort = "Latest"; //used to cancel any request in progress if another one needs to take it's place var canceler = null; - + function getActiveCategory() { if (vm.searchQuery !== "") { return ""; @@ -38,7 +42,7 @@ for (var i = 0; i < vm.categories.length; i++) { if (vm.categories[i].active === true) { return vm.categories[i].name; - } + } } return ""; } @@ -56,7 +60,7 @@ .then(function(pack) { vm.popular = pack.packages; }), - ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize) + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort) .then(function(pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); @@ -66,41 +70,6 @@ vm.loading = false; }); - $scope.$watch(function() { - return vm.searchQuery; - }, _.debounce(function (newVal, oldVal) { - $scope.$apply(function () { - if (vm.searchQuery) { - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - vm.loading = true; - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); - } - else { - canceler = $q.defer(); - } - - ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, - vm.pagination.pageSize, - "", - vm.searchQuery, - canceler) - .then(function(pack) { - vm.packages = pack.packages; - vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); - vm.pagination.pageNumber = 1; - vm.loading = false; - //set back to null so it can be re-created - canceler = null; - }); - } - } - }); - }, 200)); - } function selectCategory(selectedCategory, categories) { @@ -121,12 +90,14 @@ searchCategory = ""; } - $q.all([ + currSort = "Latest"; + + $q.all([ ourPackageRepositoryResource.getPopular(8, searchCategory) .then(function(pack) { vm.popular = pack.packages; }), - ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, searchCategory, vm.searchQuery) + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery) .then(function(pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); @@ -141,11 +112,20 @@ function showPackageDetails(selectedPackage) { ourPackageRepositoryResource.getDetails(selectedPackage.id) - .then(function(pack) { - vm.package = pack; - vm.packageViewState = "packageDetails"; + .then(function (pack) { + packageResource.validateInstalled(pack.name, pack.latestVersion) + .then(function() { + //ok, can install + vm.package = pack; + vm.package.isValid = true; + vm.packageViewState = "packageDetails"; + }, function() { + //nope, cannot install + vm.package = pack; + vm.package.isValid = false; + vm.packageViewState = "packageDetails"; + }) }); - } function setPackageViewState(state) { @@ -155,7 +135,7 @@ } function nextPage(pageNumber) { - ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, getActiveCategory(), vm.searchQuery) + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) .then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); @@ -163,7 +143,7 @@ } function prevPage(pageNumber) { - ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, getActiveCategory(), vm.searchQuery) + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) .then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); @@ -171,7 +151,7 @@ } function goToPage(pageNumber) { - ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, getActiveCategory(), vm.searchQuery) + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) .then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); @@ -187,33 +167,44 @@ vm.packageViewState = "packageInstall"; vm.loading = false; vm.localPackage = pack; - vm.localPackage.allowed = true; - }, - error); + vm.localPackage.allowed = true; + }, function (evt, status, headers, config) { + + if (status == 400) { + //it's a validation error + vm.installState.type = "error"; + vm.zipFile.serverErrorMessage = evt.message; + } + }); } function error(e, args) { - + //This will return a rejection meaning that the promise change above will stop + return $q.reject(); } function installPackage(selectedPackage) { - - vm.installState.status = "importing..."; + + vm.installState.status = localizationService.localize("packager_installStateImporting"); + vm.installState.progress = "0"; packageResource - .import(selectedPackage) + .import(selectedPackage) .then(function(pack) { - vm.installState.status = "Installing..."; + vm.installState.status = localizationService.localize("packager_installStateInstalling"); + vm.installState.progress = "33"; return packageResource.installFiles(pack); }, error) .then(function(pack) { - vm.installState.status = "Restarting, please wait..."; + vm.installState.status = localizationService.localize("packager_installStateRestarting"); + vm.installState.progress = "66"; return packageResource.installData(pack); }, error) .then(function(pack) { - vm.installState.status = "All done, your browser will now refresh"; + vm.installState.status = localizationService.localize("packager_installStateComplete"); + vm.installState.progress = "100"; return packageResource.cleanUp(pack); }, error) @@ -221,11 +212,11 @@ if (result.postInstallationPath) { //Put the redirect Uri in a cookie so we can use after reloading - window.localStorage.setItem("packageInstallUri", result.postInstallationPath); + localStorageService.set("packageInstallUri", result.postInstallationPath); } - + //reload on next digest (after cookie) - $timeout(function () { + $timeout(function() { window.location.reload(true); }); @@ -246,6 +237,46 @@ vm.lightbox = null; } + + var searchDebounced = _.debounce(function(e) { + + $scope.$apply(function () { + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } + else { + canceler = $q.defer(); + } + + currSort = vm.searchQuery ? "Default" : "Latest"; + + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, + vm.pagination.pageSize, + currSort, + "", + vm.searchQuery, + canceler) + .then(function(pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + vm.pagination.pageNumber = 1; + vm.loading = false; + //set back to null so it can be re-created + canceler = null; + }); + + }); + + }, 200); + + function search(searchQuery) { + vm.loading = true; + searchDebounced(); + } + init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html index 28d345ac85..b19eb63232 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -4,14 +4,15 @@
    - +
    - -
    +
    @@ -110,21 +123,23 @@ -
    +
    -
    +
    -
    {{ vm.package.name }}
    +
    -
    +
    {{ vm.package.name }}
    -
    - -
    - -
    -
    -
    -
    -
    - +
    + +
    + + + +
    + +
    +
    + +
    + + +
    + +
    +
    {{ vm.package.ownerInfo.owner }}
    +
    + {{ vm.package.ownerInfo.owner }} has {{ vm.package.ownerInfo.karma }} karma points +
    +
    +
    + +
    +
    Information
    -
    {{ vm.package.ownerInfo.owner }}
    -
    - {{ vm.package.ownerInfo.owner }} has {{ vm.package.ownerInfo.karma }} karma points + +
    +
    Owner:
    +
    {{vm.package.ownerInfo.owner}}
    + +
    +
    Contributors:
    +
    + {{ contributor }} +
    +
    + +
    +
    Created:
    +
    {{vm.package.created}}
    +
    + +
    +
    Current version:
    +
    {{vm.package.latestVersion}}
    +
    + +
    +
    .Net Version:
    +
    {{vm.package.information.netVersion}}
    +
    + +
    +
    License:
    +
    {{vm.package.licenseName}}
    +
    + +
    +
    Downloads:
    +
    {{vm.package.downloads}}
    +
    + +
    +
    Likes:
    +
    {{vm.package.likes}}
    +
    +
    -
    -
    -
    Information
    -
    +
    +
    Compatibility
    +
    This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100%
    +
    +
    + {{compatibility.version}} + ({{compatibility.percentage}}%) +
    + + + -
    -
    Owner:
    -
    {{vm.package.ownerInfo.owner}}
    +
    -
    -
    Contributors:
    -
    - {{ contributor.name }},   +
    +
    External sources
    + -
    -
    Created:
    -
    {{vm.package.created}}
    -
    - -
    -
    Current version:
    -
    {{vm.package.latestVersion}}
    -
    - -
    -
    .Net Version:
    -
    {{vm.package.information.netVersion}}
    -
    - -
    -
    License:
    -
    {{vm.package.licenseName}}
    -
    - -
    -
    Downloads:
    -
    {{vm.package.downloads}}
    -
    - -
    -
    Karma:
    -
    {{vm.package.ownerInfo.karma}}
    -
    - -
    -
    - -
    -
    Compatibility
    -
    This project is compatible with the following versions as reported by community members who have downloaded this package:
    -
    -
    - {{compatibility.version}} - ({{compatibility.percentage}}%) -
    -
    - -
    -
    -
    - -
    -
    External sources
    -
    -
    - + -
    -
    +
    + + + ← Take me back + + - -
    - - -
    +
    + +
    + +
    + + +
    + + +
    -
    -

    {{ vm.localPackage.name }}

    +
    +

    {{ vm.localPackage.name }}

    - + -
    - Version - {{ vm.localPackage.version }} -
    +
    + Version + {{ vm.localPackage.version }} +
    - + -
    - Read me -
    - -
    +
    + Read me +
    + +
    -
    - - -
    -
    - This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} -
    -
    -

    {{vm.installState.status}}

    -
    +
    + + +
    + +
    + + +
    + +
    + This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} +
    + +
    +

    {{vm.installState.status}}

    +
    + +
    +
    - - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index 693bd49ec6..e777b0a409 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -1,4 +1,4 @@ -function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element) { +function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) { //setup the default config var config = { @@ -22,6 +22,8 @@ function dateTimePickerController($scope, notificationsService, assetsService, a $scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD"; } + + $scope.hasDatetimePickerValue = $scope.model.value ? true : false; $scope.datetimePickerValue = null; @@ -43,20 +45,46 @@ function dateTimePickerController($scope, notificationsService, assetsService, a if (e.date && e.date.isValid()) { $scope.datePickerForm.datepicker.$setValidity("pickerError", true); $scope.hasDatetimePickerValue = true; - $scope.datetimePickerValue = e.date.format($scope.model.config.format); - $scope.model.value = $scope.datetimePickerValue; + $scope.datetimePickerValue = e.date.format($scope.model.config.format); } else { $scope.hasDatetimePickerValue = false; $scope.datetimePickerValue = null; } - + + setModelValue(); + if (!$scope.model.config.pickTime) { $element.find("div:first").datetimepicker("hide", 0); } }); } + //sets the scope model value accordingly - this is the value to be sent up to the server and depends on + // if the picker is configured to offset time. We always format the date/time in a specific format for sending + // to the server, this is different from the format used to display the date/time. + function setModelValue() { + if ($scope.hasDatetimePickerValue) { + var elementData = $element.find("div:first").data().DateTimePicker; + if ($scope.model.config.pickTime) { + //check if we are supposed to offset the time + if ($scope.model.value && $scope.model.config.offsetTime === "1" && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { + $scope.model.value = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset); + $scope.serverTime = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z"); + } + else { + $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss"); + } + } + else { + $scope.model.value = elementData.getDate().format("YYYY-MM-DD"); + } + } + else { + $scope.model.value = null; + } + } + var picker = null; $scope.clearDate = function() { @@ -66,6 +94,21 @@ function dateTimePickerController($scope, notificationsService, assetsService, a $scope.datePickerForm.datepicker.$setValidity("pickerError", true); } + $scope.serverTime = null; + $scope.serverTimeNeedsOffsetting = false; + if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { + // Will return something like 120 + var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + + // Will return something like -120 + var localOffset = new Date().getTimezoneOffset(); + + // If these aren't equal then offsetting is needed + // note the minus in front of serverOffset needed + // because C# and javascript return the inverse offset + $scope.serverTimeNeedsOffsetting = (-serverOffset !== localOffset); + } + //get the current user to see if we can localize this picker userService.getCurrentUser().then(function (user) { @@ -97,8 +140,17 @@ function dateTimePickerController($scope, notificationsService, assetsService, a }); if ($scope.hasDatetimePickerValue) { - //assign value to plugin/picker - var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment(); + var dateVal; + //check if we are supposed to offset the time + if ($scope.model.value && $scope.model.config.offsetTime === "1" && $scope.serverTimeNeedsOffsetting) { + //get the local time offset from the server + dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset); + $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z"); + } + else { + //create a normal moment , no offset required + var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment(); + } element.datetimepicker("setValue", dateVal); $scope.datetimePickerValue = dateVal.format($scope.model.config.format); @@ -117,18 +169,7 @@ function dateTimePickerController($scope, notificationsService, assetsService, a var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - if ($scope.hasDatetimePickerValue) { - var elementData = $element.find("div:first").data().DateTimePicker; - if ($scope.model.config.pickTime) { - $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss"); - } - else { - $scope.model.value = elementData.getDate().format("YYYY-MM-DD"); - } - } - else { - $scope.model.value = null; - } + setModelValue(); }); //unbind doc click event! $scope.$on('$destroy', function () { @@ -142,17 +183,7 @@ function dateTimePickerController($scope, notificationsService, assetsService, a }); var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - if ($scope.hasDatetimePickerValue) { - if ($scope.model.config.pickTime) { - $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD HH:mm:ss"); - } - else { - $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD"); - } - } - else { - $scope.model.value = null; - } + setModelValue(); }); //unbind doc click event! diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index e6d49e5c6c..003e2ada60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -18,6 +18,10 @@ {{datePickerForm.datepicker.errorMsg}} Invalid date +

    + This translates to the following time on the server: {{serverTime}}
    + What does this mean? +

    Clear date

    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 4285cf0f77..df21541f09 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -71,7 +71,10 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag "GetBigThumbnail", [{ originalImagePath: file.file }]); + var extension = file.file.substring(file.file.lastIndexOf(".") + 1, file.file.length); + file.thumbnail = thumbnailUrl; + file.extension = extension.toLowerCase(); }); $scope.clearFiles = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index c213378b23..0905de07f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -1,10 +1,15 @@ 
    - +
    • - - {{file.file}} + + {{file.file}} + {{file.file}} + + + .{{file.extension}} + {{file.file}}
    • diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index b1343a960b..093afd3c73 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -75,6 +75,7 @@ checklist-model="currentCell.allowed" checklist-value="editor.alias"> {{editor.name}} + ({{editor.alias}})
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html index 339ee9d455..feaed67ed9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html @@ -114,7 +114,7 @@
    -
    +
    + +
    No content has been added
    +
    No members have been added
    +
    + @@ -18,7 +25,7 @@ @@ -68,7 +75,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 96bebb7abf..29a9875df8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -49,6 +49,13 @@ on-sort="vm.sort"> + +
    No content has been added
    +
    No members have been added
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 594475a7d2..0a1a14fc44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -151,7 +151,6 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie layouts: $scope.model.config.layouts, activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) }, - orderBySystemField: true, allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, @@ -159,6 +158,15 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete }; + // Check if selected order by field is actually custom field + for (var j = 0; j < $scope.options.includeProperties.length; j++) { + var includedProperty = $scope.options.includeProperties[j]; + if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) { + $scope.options.orderBySystemField = includedProperty.isSystem === 1; + break; + } + } + //update all of the system includeProperties to enable sorting _.each($scope.options.includeProperties, function (e, i) { @@ -169,62 +177,60 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie e.allowSorting = true; } - // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it - if (e.isSystem && $scope.entityType == "media") { - e.allowSorting = e.alias != 'updateDate'; - } - // Another special case for members, only fields on the base table (cmsMember) can be used for sorting if (e.isSystem && $scope.entityType == "member") { e.allowSorting = e.alias == 'username' || e.alias == 'email'; } if (e.isSystem) { - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; - }); - } + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } }); $scope.selectLayout = function (selectedLayout) { $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); }; - function showNotificationsAndReset(err, reload, successMsg) { + function showNotificationsAndReset(err, reload, successMsg) { - //check if response is ysod - if (err.status && err.status >= 500) { + //check if response is ysod + if (err.status && err.status >= 500) { - // Open ysod overlay - $scope.ysodOverlay = { - view: "ysod", - error: err, - show: true - }; - } + // Open ysod overlay + $scope.ysodOverlay = { + view: "ysod", + error: err, + show: true + }; + } - $timeout(function () { - $scope.bulkStatus = ""; - $scope.actionInProgress = false; - }, 500); + $timeout(function() { + $scope.bulkStatus = ""; + $scope.actionInProgress = false; + }, + 500); - if (reload === true) { - $scope.reloadView($scope.contentId); - } + if (reload === true) { + $scope.reloadView($scope.contentId); + } - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - else if (successMsg) { - notificationsService.success("Done", successMsg); - } - } + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } else if (successMsg) { + localizationService.localize("bulk_done") + .then(function(v) { + notificationsService.success(v, successMsg); + }); + } + } - $scope.next = function (pageNumber) { + $scope.next = function (pageNumber) { $scope.options.pageNumber = pageNumber; $scope.reloadView($scope.contentId); }; @@ -365,65 +371,87 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie }); } - $scope.delete = function () { + $scope.delete = function() { + var confirmDeleteText = ""; - var attempt = - applySelected( - function(selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function(count, total) { - return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); - }, - function(total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, - "Sure you want to delete?"); - if (attempt) { - attempt.then(function () { - //executes if all is successful, let's sync the tree - var activeNode = appState.getTreeState("selectedNode"); - if (activeNode) { - navigationService.reloadNode(activeNode); - } - }); - } - }; + localizationService.localize("defaultdialogs_confirmdelete") + .then(function(value) { + confirmDeleteText = value; + + var attempt = + applySelected( + function(selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function(count, total) { + var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function(total) { + var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); + return localizationService.localize(key, [total]); + }, + confirmDeleteText + "?"); + if (attempt) { + attempt.then(function() { + //executes if all is successful, let's sync the tree + var activeNode = appState.getTreeState("selectedNode"); + if (activeNode) { + navigationService.reloadNode(activeNode); + } + }); + } + }); + }; $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { + var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function (total) { + var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); + return localizationService.localize(key, [total]); + }); }; - $scope.unpublish = function () { - applySelected( - function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); - }; + $scope.unpublish = function() { + applySelected( + function(selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, + function(count, total) { + var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function(total) { + var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); + return localizationService.localize(key, [total]); + }); + }; - $scope.move = function () { - $scope.moveDialog = {}; - $scope.moveDialog.title = "Move"; - $scope.moveDialog.section = $scope.entityType; - $scope.moveDialog.currentNode = $scope.contentId; - $scope.moveDialog.view = "move"; - $scope.moveDialog.show = true; + $scope.move = function() { + $scope.moveDialog = {}; + $scope.moveDialog.title = localizationService.localize("general_move"); + $scope.moveDialog.section = $scope.entityType; + $scope.moveDialog.currentNode = $scope.contentId; + $scope.moveDialog.view = "move"; + $scope.moveDialog.show = true; - $scope.moveDialog.submit = function (model) { + $scope.moveDialog.submit = function(model) { - if (model.target) { - performMove(model.target); - } + if (model.target) { + performMove(model.target); + } - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; - $scope.moveDialog.close = function (oldModel) { - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; + $scope.moveDialog.close = function(oldModel) { + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; - }; + }; function performMove(target) { @@ -434,33 +462,46 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie var newPath = null; applySelected( function(selected, index) { - return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }).then(function(path) { - newPath = path; - return path; - }); + return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }) + .then(function(path) { + newPath = path; + return path; + }); }, - function(count, total) {return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : "");}, - function(total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }) - .then(function() { + function(count, total) { + var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function(total) { + var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems"); + return localizationService.localize(key, [total]); + }) + .then(function() { //executes if all is successful, let's sync the tree if (newPath) { //we need to do a double sync here: first refresh the node where the content was moved, // then refresh the node where the content was moved from - navigationService.syncTree({ tree: target.nodeType, path: newPath, forceReload: true, activate: false }).then(function (args) { - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - if (activeNode) { - navigationService.reloadNode(activeNode); - } - }); + navigationService.syncTree({ + tree: target.nodeType, + path: newPath, + forceReload: true, + activate: false + }) + .then(function(args) { + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + if (activeNode) { + navigationService.reloadNode(activeNode); + } + }); } }); } $scope.copy = function () { $scope.copyDialog = {}; - $scope.copyDialog.title = "Copy"; + $scope.copyDialog.title = localizationService.localize("general_copy"); $scope.copyDialog.section = $scope.entityType; $scope.copyDialog.currentNode = $scope.contentId; $scope.copyDialog.view = "copy"; @@ -485,8 +526,14 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie function performCopy(target, relateToOriginal) { applySelected( function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, - function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); + function (count, total) { + var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function (total) { + var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); + return localizationService.localize(key, [total]); + }); } function getCustomPropertyValue(alias, properties) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 742d1251cb..3969483734 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -136,7 +136,7 @@ ng-if="options.allowBulkDelete && (buttonPermissions == null || buttonPermissions.canDelete)" type="button" button-style="link" - label="Delete" + label="Delete" label-key="actions_delete" icon="icon-trash" action="delete()" @@ -162,14 +162,16 @@ - - +
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index 4498e281dd..333b91f81f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -1,40 +1,95 @@ -function sortByPreValsController($rootScope, $scope, localizationService) { +function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) { + //Get the prevalue from the correct place + function getPrevalues() { + if (editorState.current.preValues) { + return editorState.current.preValues; + } + else { + return listViewPrevalueHelper.getPrevalues(); + } + } - $scope.sortByFields = [ - { value: "SortOrder", key: "general_sort" }, - { value: "Name", key: "general_name" }, - { value: "VersionDate", key: "content_updateDate" }, - { value: "Updater", key: "content_updatedBy" }, - { value: "CreateDate", key: "content_createDate" }, - { value: "Owner", key: "content_createBy" }, - { value: "ContentTypeAlias", key: "content_documentType" }, - { value: "Published", key: "content_isPublished" }, - { value: "Email", key: "general_email" }, - { value: "Username", key: "general_username" } - ]; - - //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared - _.each($scope.sortByFields, function (e, i) { - localizationService.localize(e.key).then(function (v) { - e.name = v; + //Watch the prevalues + $scope.$watch(function () { + return _.findWhere(getPrevalues(), { key: "includeProperties" }).value; + }, function () { + populateFields(); + }, true); //Use deep watching, otherwise we won't pick up header changes - switch (e.value) { - case "Updater": - e.name += " (Content only)"; - break; - case "Published": - e.name += " (Content only)"; - break; - case "Email": - e.name += " (Members only)"; - break; - case "Username": - e.name += " (Members only)"; - break; + function populateFields() { + // Helper to find a particular value from the list of sort by options + function findFromSortByFields(value) { + return _.find($scope.sortByFields, function (e) { + return e.value.toLowerCase() === value.toLowerCase(); + }); + } + + // Get list of properties assigned as columns of the list view + var propsPreValue = _.findWhere(getPrevalues(), { key: "includeProperties" }); + + // Populate list of options for the default sort (all the columns plus then node name) + $scope.sortByFields = []; + $scope.sortByFields.push({ value: "name", name: "Name", isSystem: 1 }); + if (propsPreValue != undefined) { + for (var i = 0; i < propsPreValue.value.length; i++) { + var value = propsPreValue.value[i]; + $scope.sortByFields.push({ + value: value.alias, + name: value.header, + isSystem: value.isSystem + }); } - }); - }); + } + // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared + var systemFields = [ + { value: "SortOrder", key: "general_sort" }, + { value: "Name", key: "general_name" }, + { value: "VersionDate", key: "content_updateDate" }, + { value: "Updater", key: "content_updatedBy" }, + { value: "CreateDate", key: "content_createDate" }, + { value: "Owner", key: "content_createBy" }, + { value: "ContentTypeAlias", key: "content_documentType" }, + { value: "Published", key: "content_isPublished" }, + { value: "Email", key: "general_email" }, + { value: "Username", key: "general_username" } + ]; + _.each(systemFields, function (e) { + localizationService.localize(e.key).then(function (v) { + + var sortByListValue = findFromSortByFields(e.value); + if (sortByListValue) { + sortByListValue.name = v; + switch (e.value) { + case "Updater": + e.name += " (Content only)"; + break; + case "Published": + e.name += " (Content only)"; + break; + case "Email": + e.name += " (Members only)"; + break; + case "Username": + e.name += " (Members only)"; + break; + } + } + }); + }); + + // Check existing model value is available in list and ensure a value is set + var existingValue = findFromSortByFields($scope.model.value); + if (existingValue) { + // Set the existing value + // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set + $scope.model.value = existingValue.value; + } + else { + // Existing value not found, set to first value + $scope.model.value = $scope.sortByFields[0].value; + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html index 2b35aa2ca4..754afd9b60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html @@ -1,5 +1,4 @@  \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 096685f1a9..3cb01386ba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -3,10 +3,16 @@