diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9dc6f9457f..1431f744c6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,4 @@ -_Looking for Umbraco version 8? [Click here](https://github.com/umbraco/Umbraco-CMS/blob/temp8/.github/V8_GETTING_STARTED.md) to go to the v8 branch_ +_Looking for Umbraco version 8? [Click here](https://github.com/umbraco/Umbraco-CMS/blob/dev-v8/.github/V8_GETTING_STARTED.md) to go to the v8 branch_ # Contributing to Umbraco CMS 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md index 422dd1f664..1f7e827dc8 100644 --- a/.github/CONTRIBUTING_DETAILED.md +++ b/.github/CONTRIBUTING_DETAILED.md @@ -103,11 +103,9 @@ There's two big areas that you should know about: 1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes. You may need to run the following commands to set up gulp properly: ``` - npm cache clean - npm install -g gulp - npm install -g gulp-cli + npm cache clean --force npm install - gulp build + npm run build ``` 2. "The rest" is a C# based codebase, with some traces of our WebForms past but mostly ASP.NET MVC based these days. You can make changes, build them in Visual Studio, and hit `F5` to see the result. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8cb9017518..55b12e2317 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ - [ ] I have added steps to test this contribution in the description below -If there's an existing issue for this PR then this fixes: +If there's an existing issue for this PR then this fixes ### Description diff --git a/.github/README.md b/.github/README.md index a60eed0a97..1c1f5e3c41 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,7 +1,9 @@ -_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._ -[![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge) +[![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=dev-v8)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge) -_Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._ + +_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._ + +_Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS/tree/dev-v7) to go to the v7 branch._ _Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md)._ diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index eaf4aa334e..49a898cfe1 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -5,7 +5,7 @@ * [Visual Studio 2017 Community (Free)](https://www.visualstudio.com/vs/community/), or Professional, Enterprise, etc... _(**Minimum of Version 15.7** or higher, this is important, you WILL get issues with lesser versions)_ * .NET Framework 4.7.2 installed, get it here: https://www.microsoft.com/net/download/thank-you/net472?survey=false * .NET Framework 4.7.2 developer pack, get it here: https://www.microsoft.com/net/download/thank-you/net472-developer-pack _(be sure this is the ENU file which will be named `NDP472-DevPack-ENU.exe`)_ -* Clone the Umbraco repository using the `temp8` branch. If your git client doesn't support specifying the branch as you clone then use the command `git clone --single-branch -b temp8 `. _(If you clone the repo using the default v7 branch and then checkout the `temp8` branch you **might** get problems)_ +* Clone the Umbraco repository using the `dev-v8` branch. If your git client doesn't support specifying the branch as you clone then use the command `git clone --single-branch -b dev-v8 `. _(If you clone the repo using the default v7 branch and then checkout the `dev-v8` branch you **might** get problems)_ ### Start the solution diff --git a/.gitignore b/.gitignore index 57b4e18aaa..23ba01ebf0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,160 +1,163 @@ -# -# Umbraco Cms Git Ignore -# - -# common files -*.obj -*.pdb -*.user -*.aps -*.pch -*.vspscc -*.orig -*.suo -*.vs10x -*.ndproj +# +# Umbraco Cms Git Ignore +# + +# common files +*.obj +*.pdb +*.user +*.aps +*.pch +*.vspscc +*.orig +*.suo +*.vs10x +*.ndproj *.ignorer.* - -# common directories -.DS_Store -._.DS_Store -.vs/ -/local/ - -# build directories -[Bb]in/ -[Db]ebug*/ -[Rr]elease*/ -obj/ - -# tools -_ReSharper*/ -_NCrunch_*/ -*.ncrunchsolution -*.ncrunchsolution.user -*.ncrunchproject -*.crunchsolution.cache -tools/NDepend/ - - - - -[Tt]est[Rr]esult* -[Bb]uild[Ll]og.* -*.[Pp]ublish.xml -[sS]ource -[sS]andbox -umbraco.config -App_Data/TEMP/* -[Uu]mbraco/[Pp]resentation/[Uu]mbraco/[Pp]lugins/* -[Uu]mbraco/[Pp]resentation/[Uu]ser[Cc]ontrols/* -[Uu]mbraco/[Pp]resentation/[Ss]cripts/* -[Uu]mbraco/[Pp]resentation/[Ff]onts/* -[Uu]mbraco/[Pp]resentation/[Cc]ss/* - -src/Umbraco.Web.UI/[Cc]ss/* -src/Umbraco.Web.UI/App_Code/* -src/Umbraco.Web.UI/App_Data/* -src/Umbraco.Tests/App_Data/* -src/Umbraco.Web.UI/[Mm]edia/* -src/Umbraco.Web.UI/[Mm]aster[Pp]ages/* -src/Umbraco.Web.UI/[Mm]acro[Ss]cripts/* -!src/Umbraco.Web.UI/[Mm]acro[Ss]cripts/[Ww]eb.[Cc]onfig -src/Umbraco.Web.UI/[Xx]slt/* -src/Umbraco.Web.UI/[Ii]mages/* -src/Umbraco.Web.UI/[Ss]cripts/* -src/Umbraco.Web.UI/Web.*.config.transformed - -umbraco/presentation/umbraco/plugins/uComponents/uComponentsInstaller.ascx -umbraco/presentation/packages/uComponents/MultiNodePicker/CustomTreeService.asmx - -src/Umbraco.Tests/config/applications.config -src/Umbraco.Tests/config/trees.config -src/Umbraco.Web.UI/web.config -src/Umbraco.Web.UI/[Cc]onfig/ClientDependency.config -src/Umbraco.Tests/config/404handlers.config -src/Umbraco.Web.UI/[Vv]iews/*.cshtml -src/Umbraco.Web.UI/[Vv]iews/*.vbhtml -src/Umbraco.Tests/[Cc]onfig/umbracoSettings.config -src/Umbraco.Web.UI/[Vv]iews/* -src/packages/ -src/packages/repositories.config - -src/Umbraco.Web.UI/[Ww]eb.config -*.transformed - -node_modules -lib-bower - -src/Umbraco.Web.UI/[Uu]mbraco/[Ll]ib/* -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/umbraco.* -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/*.loader.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/init.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/routes.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.dev.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js -src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/canvasdesigner.*.js - -src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/ -src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js -src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.css -src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.html -src/Umbraco.Web.UI/[Uu]mbraco/[Aa]ssets/* -src/Umbraco.Web.UI.Client/[Bb]uild/* -src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/ -src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/ - -src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css -src/Umbraco.Web.UI.Client/vwd.webinfo - -src/Umbraco.Web.UI/App_Plugins/* -src/*.psess -src/*.vspx - -NDependOut/* -QueryResult.htm - -src/Umbraco.Web.UI/[Cc]onfig/appSettings.config -src/Umbraco.Web.UI/[Cc]onfig/connectionStrings.config -src/Umbraco.Web.UI/[Uu]mbraco/plugins/* -build/ApiDocs/* -build/ApiDocs/Output/* -src/Umbraco.Web.UI.Client/bower_components/* -/src/Umbraco.Web.UI/[Uu]mbraco/preview -/src/Umbraco.Web.UI/[Uu]mbraco/preview.old - -# ignore rule for clearing out Belle (avoid rebuilding all the time) -preserve.belle - -#Ignore Rule for output of generated documentation files from Grunt docserve -src/Umbraco.Web.UI.Client/docs/api -src/*.boltdata/ -src/umbraco.sln.ide/* -src/.vs/ - -src/Umbraco.Web.UI/[Jj]s/* -src/Umbraco.Tests/[Mm]edia -tools/docfx/* -apidocs/_site/* -src/*/project.lock.json -src/.idea/* - -apidocs/api/* -build/docs.zip -build/ui-docs.zip -build/csharp-docs.zip -src/packages/ -src/PrecompiledWeb/* - -# build -build.out/ -build.tmp/ -build/hooks/ -build/temp/ - - - + +# common directories +.DS_Store +._.DS_Store +.vs/ +/local/ + +# build directories +[Bb]in/ +[Db]ebug*/ +[Rr]elease*/ +obj/ + +# tools +_ReSharper*/ +_NCrunch_*/ +*.ncrunchsolution +*.ncrunchsolution.user +*.ncrunchproject +*.crunchsolution.cache +tools/NDepend/ + + + + +[Tt]est[Rr]esult* +[Bb]uild[Ll]og.* +*.[Pp]ublish.xml +[sS]ource +[sS]andbox +umbraco.config +App_Data/TEMP/* +[Uu]mbraco/[Pp]resentation/[Uu]mbraco/[Pp]lugins/* +[Uu]mbraco/[Pp]resentation/[Uu]ser[Cc]ontrols/* +[Uu]mbraco/[Pp]resentation/[Ss]cripts/* +[Uu]mbraco/[Pp]resentation/[Ff]onts/* +[Uu]mbraco/[Pp]resentation/[Cc]ss/* + +src/Umbraco.Web.UI/[Cc]ss/* +src/Umbraco.Web.UI/App_Code/* +src/Umbraco.Web.UI/App_Data/* +src/Umbraco.Tests/App_Data/* +src/Umbraco.Web.UI/[Mm]edia/* +src/Umbraco.Web.UI/[Mm]aster[Pp]ages/* +src/Umbraco.Web.UI/[Mm]acro[Ss]cripts/* +!src/Umbraco.Web.UI/[Mm]acro[Ss]cripts/[Ww]eb.[Cc]onfig +src/Umbraco.Web.UI/[Xx]slt/* +src/Umbraco.Web.UI/[Ii]mages/* +src/Umbraco.Web.UI/[Ss]cripts/* +src/Umbraco.Web.UI/Web.*.config.transformed + +umbraco/presentation/umbraco/plugins/uComponents/uComponentsInstaller.ascx +umbraco/presentation/packages/uComponents/MultiNodePicker/CustomTreeService.asmx + +src/Umbraco.Tests/config/applications.config +src/Umbraco.Tests/config/trees.config +src/Umbraco.Web.UI/web.config +src/Umbraco.Web.UI/[Cc]onfig/ClientDependency.config +src/Umbraco.Web.UI/[Cc]onfig/serilog.config +src/Umbraco.Web.UI/[Cc]onfig/serilog.user.config +src/Umbraco.Tests/config/404handlers.config +src/Umbraco.Web.UI/[Vv]iews/*.cshtml +src/Umbraco.Web.UI/[Vv]iews/*.vbhtml +src/Umbraco.Tests/[Cc]onfig/umbracoSettings.config +src/Umbraco.Web.UI/[Vv]iews/* +src/packages/ +src/packages/repositories.config + +src/Umbraco.Web.UI/[Ww]eb.config +*.transformed + +node_modules +lib-bower + +src/Umbraco.Web.UI/[Uu]mbraco/[Ll]ib/* +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/umbraco.* +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/*.loader.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/init.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/routes.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.dev.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/canvasdesigner.*.js + +src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/ +src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js +src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.css +src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.html +src/Umbraco.Web.UI/[Uu]mbraco/[Aa]ssets/* +src/Umbraco.Web.UI.Client/[Bb]uild/* +src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/ +src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/ + +src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css +src/Umbraco.Web.UI.Client/vwd.webinfo + +src/Umbraco.Web.UI/App_Plugins/* +src/*.psess +src/*.vspx + +NDependOut/* +QueryResult.htm + +src/Umbraco.Web.UI/[Cc]onfig/appSettings.config +src/Umbraco.Web.UI/[Cc]onfig/connectionStrings.config +src/Umbraco.Web.UI/[Uu]mbraco/plugins/* +build/ApiDocs/* +build/ApiDocs/Output/* +src/Umbraco.Web.UI.Client/bower_components/* +/src/Umbraco.Web.UI/[Uu]mbraco/preview +/src/Umbraco.Web.UI/[Uu]mbraco/preview.old + +# ignore rule for clearing out Belle (avoid rebuilding all the time) +preserve.belle + +#Ignore Rule for output of generated documentation files from Grunt docserve +src/Umbraco.Web.UI.Client/docs/api +src/*.boltdata/ +src/umbraco.sln.ide/* +src/.vs/ + +src/Umbraco.Web.UI/[Jj]s/* +src/Umbraco.Tests/[Mm]edia +tools/docfx/* +apidocs/_site/* +src/*/project.lock.json +src/.idea/* + +apidocs/api/* +build/docs.zip +build/ui-docs.zip +build/csharp-docs.zip +src/packages/ +src/PrecompiledWeb/* + +# build +build.out/ +build.tmp/ +build/hooks/ +build/temp/ + + + # eof +/src/Umbraco.Web.UI.Client/TESTS-*.xml diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 9758c05dd6..b257f8dcdd 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms.Core 8.0.0 Umbraco Cms Core Binaries @@ -15,31 +15,35 @@ en-US umbraco - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index fabd1e25a8..79113a7c94 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms.Web 8.0.0 Umbraco Cms Core Binaries @@ -15,30 +15,35 @@ en-US umbraco - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 1504002245..9578ef96d9 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms 8.0.0 Umbraco Cms @@ -15,18 +15,25 @@ en-US umbraco - - - - - - - - + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 2da3206e7a..14778e0f10 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -126,45 +126,55 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 deleted file mode 100644 index 9b609a498e..0000000000 --- a/build/NuSpecs/tools/install.core.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -Write-Host "installPath:" "${installPath}" -Write-Host "toolsPath:" "${toolsPath}" - -Write-Host " " - -if ($project) { - $dateTime = Get-Date -Format yyyyMMdd-HHmmss - - # Create paths and list them - $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName - Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" - Write-Host "backupPath:" "${backupPath}" - $copyLogsPath = Join-Path $backupPath "CopyLogs" - Write-Host "copyLogsPath:" "${copyLogsPath}" - $umbracoBinFolder = Join-Path $projectPath "bin" - Write-Host "umbracoBinFolder:" "${umbracoBinFolder}" - - # Create backup folder and logs folder if it doesn't exist yet - New-Item -ItemType Directory -Force -Path $backupPath - New-Item -ItemType Directory -Force -Path $copyLogsPath - - # After backing up, remove all umbraco dlls from bin folder in case dll files are included in the VS project - # See: http://issues.umbraco.org/issue/U4-4930 - - if(Test-Path $umbracoBinFolder) { - $umbracoBinBackupPath = Join-Path $backupPath "bin" - - New-Item -ItemType Directory -Force -Path $umbracoBinBackupPath - - robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log - - # Delete files Umbraco ships with - if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Web.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Examine.dll) { Remove-Item $umbracoBinFolder\Umbraco.Examine.dll -Force -Confirm:$false } - - # Delete files Umbraco depends upon - $amd64Folder = Join-Path $umbracoBinFolder "amd64" - if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false } - $x86Folder = Join-Path $umbracoBinFolder "x86" - if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false } - if(Test-Path $umbracoBinFolder\AutoMapper.dll) { Remove-Item $umbracoBinFolder\AutoMapper.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\AutoMapper.Net4.dll) { Remove-Item $umbracoBinFolder\AutoMapper.Net4.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ClientDependency.Core.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ClientDependency.Core.Mvc.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.Mvc.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\CookComputing.XmlRpcV2.dll) { Remove-Item $umbracoBinFolder\CookComputing.XmlRpcV2.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Examine.dll) { Remove-Item $umbracoBinFolder\Examine.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\HtmlAgilityPack.dll) { Remove-Item $umbracoBinFolder\HtmlAgilityPack.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ICSharpCode.SharpZipLib.dll) { Remove-Item $umbracoBinFolder\ICSharpCode.SharpZipLib.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Infrastructure.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Infrastructure.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Helpers.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Owin.dll) { Remove-Item $umbracoBinFolder\Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Semver.dll) { Remove-Item $umbracoBinFolder\Semver.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Collections.Immutable.dll) { Remove-Item $umbracoBinFolder\System.Collections.Immutable.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Reflection.Metadata.dll) { Remove-Item $umbracoBinFolder\System.Reflection.Metadata.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Net.Http.Extensions.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Extensions.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Net.Http.Formatting.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Formatting.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Net.Http.Primitives.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Primitives.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\System.Web.Helpers.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Http.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Http.WebHost.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.WebHost.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Mvc.dll) { Remove-Item $umbracoBinFolder\System.Web.Mvc.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.Razor.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.Deployment.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Deployment.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Razor.dll -Force -Confirm:$false } - } -} diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 9cc7bd5b26..1411cb5c97 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -11,37 +11,15 @@ if ($project) { # Create paths and list them $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" - Write-Host "backupPath:" "${backupPath}" - $copyLogsPath = Join-Path $backupPath "CopyLogs" - Write-Host "copyLogsPath:" "${copyLogsPath}" $webConfigSource = Join-Path $projectPath "Web.config" Write-Host "webConfigSource:" "${webConfigSource}" $configFolder = Join-Path $projectPath "Config" Write-Host "configFolder:" "${configFolder}" - # Create backup folder and logs folder if it doesn't exist yet - New-Item -ItemType Directory -Force -Path $backupPath - New-Item -ItemType Directory -Force -Path $copyLogsPath - - # Create a backup of original web.config - Copy-Item $webConfigSource $backupPath -Force - - # Backup config files folder - if(Test-Path $configFolder) { - $umbracoBackupPath = Join-Path $backupPath "Config" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - - robocopy $configFolder $umbracoBackupPath /e /LOG:$copyLogsPath\ConfigBackup.log - } - # Copy umbraco and umbraco_files from package to project folder $umbracoFolder = Join-Path $projectPath "Umbraco" New-Item -ItemType Directory -Force -Path $umbracoFolder $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco" - $umbracoBackupPath = Join-Path $backupPath "Umbraco" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log $copyWebconfig = $true @@ -54,7 +32,7 @@ if ($project) { [xml]$config = Get-Content $destinationWebConfig $config.configuration.appSettings.ChildNodes | ForEach-Object { - if($_.key -eq "umbracoConfigurationStatus") + if($_.key -eq "Umbraco.Core.ConfigurationStatus") { # The web.config has an umbraco-specific appSetting in it # don't overwrite it and let config transforms do their thing @@ -100,11 +78,6 @@ if ($project) { } } - $installFolder = Join-Path $projectPath "Install" - if(Test-Path $installFolder) { - Remove-Item $installFolder -Force -Recurse -Confirm:$false - } - # Open appropriate readme if($copyWebconfig -eq $true) { diff --git a/build/build.ps1 b/build/build.ps1 index d811c2cc21..c186d9adaa 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -15,7 +15,12 @@ [Parameter(Mandatory=$false)] [Alias("c")] [Alias("cont")] - [switch] $continue = $false + [switch] $continue = $false, + + # execute a command + [Parameter(Mandatory=$false, ValueFromRemainingArguments=$true)] + [String[]] + $command ) # ################################################################ @@ -475,7 +480,11 @@ # run if (-not $get) { - $ubuild.Build() + if ($command.Length -eq 0) + { + $command = @( "Build" ) + } + $ubuild.RunMethod($command); if ($ubuild.OnError()) { return } } if ($get) { return $ubuild } diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index ce40bd9baa..9ed398d52f 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -2,7 +2,7 @@ using System.Resources; [assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2018")] +[assembly: AssemblyCopyright("Copyright © Umbraco 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.0.0")] -[assembly: AssemblyInformationalVersion("8.0.0-alpha.58")] +[assembly: AssemblyFileVersion("8.1.0")] +[assembly: AssemblyInformationalVersion("8.1.0")] diff --git a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs index f1932f97ce..eeebcd2622 100644 --- a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Web; namespace Umbraco.Core.Cache @@ -110,7 +111,7 @@ namespace Umbraco.Core.Cache #region Lock - private bool _entered; + private const string ContextItemsLockKey = "Umbraco.Core.Cache.HttpRequestCache::LockEntered"; protected override void EnterReadLock() => EnterWriteLock(); @@ -118,7 +119,13 @@ namespace Umbraco.Core.Cache { if (HasContextItems) { - System.Threading.Monitor.Enter(ContextItems.SyncRoot, ref _entered); + // note: cannot keep 'entered' as a class variable here, + // since there is one per request - so storing it within + // ContextItems - which is locked, so this should be safe + + var entered = false; + Monitor.Enter(ContextItems.SyncRoot, ref entered); + ContextItems[ContextItemsLockKey] = entered; } } @@ -126,11 +133,10 @@ namespace Umbraco.Core.Cache protected override void ExitWriteLock() { - if (_entered) - { - _entered = false; - System.Threading.Monitor.Exit(ContextItems.SyncRoot); - } + var entered = (bool?) ContextItems[ContextItemsLockKey] ?? false; + if (entered) + Monitor.Exit(ContextItems.SyncRoot); + ContextItems.Remove(ContextItemsLockKey); } #endregion diff --git a/src/Umbraco.Core/Components/ICoreComposer.cs b/src/Umbraco.Core/Components/ICoreComposer.cs deleted file mode 100644 index 94aa9953a9..0000000000 --- a/src/Umbraco.Core/Components/ICoreComposer.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Core.Components -{ - /// - /// Represents a core . - /// - /// - /// All core composers are required by (compose before) all user composers, - /// and require (compose after) all runtime composers. - /// - [ComposeAfter(typeof(IRuntimeComposer))] - public interface ICoreComposer : IComposer - { } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/IRuntimeComposer.cs b/src/Umbraco.Core/Components/IRuntimeComposer.cs deleted file mode 100644 index 4b8253ee6c..0000000000 --- a/src/Umbraco.Core/Components/IRuntimeComposer.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Core.Components -{ - /// - /// Represents a runtime . - /// - /// - /// All runtime composers are required by (compose before) all core composers - /// - public interface IRuntimeComposer : IComposer - { } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/AuditEventsComponent.cs b/src/Umbraco.Core/Compose/AuditEventsComponent.cs similarity index 99% rename from src/Umbraco.Core/Components/AuditEventsComponent.cs rename to src/Umbraco.Core/Compose/AuditEventsComponent.cs index 08d4702afa..15fdfeacff 100644 --- a/src/Umbraco.Core/Components/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Compose/AuditEventsComponent.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Web; +using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -10,7 +11,7 @@ using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Compose { public sealed class AuditEventsComponent : IComponent { diff --git a/src/Umbraco.Core/Components/AuditEventsComposer.cs b/src/Umbraco.Core/Compose/AuditEventsComposer.cs similarity index 63% rename from src/Umbraco.Core/Components/AuditEventsComposer.cs rename to src/Umbraco.Core/Compose/AuditEventsComposer.cs index 692cb6c6dd..339d106087 100644 --- a/src/Umbraco.Core/Components/AuditEventsComposer.cs +++ b/src/Umbraco.Core/Compose/AuditEventsComposer.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Components +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Compose { public sealed class AuditEventsComposer : ComponentComposer, ICoreComposer { } diff --git a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs b/src/Umbraco.Core/Compose/ManifestWatcherComponent.cs similarity index 95% rename from src/Umbraco.Core/Components/ManifestWatcherComponent.cs rename to src/Umbraco.Core/Compose/ManifestWatcherComponent.cs index 2420e1d5bb..4821a1dd94 100644 --- a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs +++ b/src/Umbraco.Core/Compose/ManifestWatcherComponent.cs @@ -1,9 +1,10 @@ using System.IO; +using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Compose { public sealed class ManifestWatcherComponent : IComponent { diff --git a/src/Umbraco.Core/Components/ManifestWatcherComposer.cs b/src/Umbraco.Core/Compose/ManifestWatcherComposer.cs similarity index 71% rename from src/Umbraco.Core/Components/ManifestWatcherComposer.cs rename to src/Umbraco.Core/Compose/ManifestWatcherComposer.cs index b08680156b..fb37e3dc25 100644 --- a/src/Umbraco.Core/Components/ManifestWatcherComposer.cs +++ b/src/Umbraco.Core/Compose/ManifestWatcherComposer.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Components +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Compose { [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public class ManifestWatcherComposer : ComponentComposer, ICoreComposer diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs similarity index 98% rename from src/Umbraco.Core/Components/RelateOnCopyComponent.cs rename to src/Umbraco.Core/Compose/RelateOnCopyComponent.cs index d38406f970..63a7e170da 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Compose { // TODO: This should just exist in the content service/repo! public sealed class RelateOnCopyComponent : IComponent diff --git a/src/Umbraco.Core/Components/RelateOnCopyComposer.cs b/src/Umbraco.Core/Compose/RelateOnCopyComposer.cs similarity index 71% rename from src/Umbraco.Core/Components/RelateOnCopyComposer.cs rename to src/Umbraco.Core/Compose/RelateOnCopyComposer.cs index f5e9423edd..3c65b291c6 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComposer.cs +++ b/src/Umbraco.Core/Compose/RelateOnCopyComposer.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Components +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Compose { [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class RelateOnCopyComposer : ComponentComposer, ICoreComposer diff --git a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs similarity index 99% rename from src/Umbraco.Core/Components/RelateOnTrashComponent.cs rename to src/Umbraco.Core/Compose/RelateOnTrashComponent.cs index 6279bb98ba..501907a6e7 100644 --- a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Compose { public sealed class RelateOnTrashComponent : IComponent { diff --git a/src/Umbraco.Core/Components/RelateOnTrashComposer.cs b/src/Umbraco.Core/Compose/RelateOnTrashComposer.cs similarity index 71% rename from src/Umbraco.Core/Components/RelateOnTrashComposer.cs rename to src/Umbraco.Core/Compose/RelateOnTrashComposer.cs index 5d89bc0e37..d0e90fba33 100644 --- a/src/Umbraco.Core/Components/RelateOnTrashComposer.cs +++ b/src/Umbraco.Core/Compose/RelateOnTrashComposer.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Components +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Compose { [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class RelateOnTrashComposer : ComponentComposer, ICoreComposer diff --git a/src/Umbraco.Core/Components/ComponentCollection.cs b/src/Umbraco.Core/Composing/ComponentCollection.cs similarity index 92% rename from src/Umbraco.Core/Components/ComponentCollection.cs rename to src/Umbraco.Core/Composing/ComponentCollection.cs index 4fa81b9760..9b5319dc41 100644 --- a/src/Umbraco.Core/Components/ComponentCollection.cs +++ b/src/Umbraco.Core/Composing/ComponentCollection.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Represents the collection of implementations. @@ -24,7 +23,7 @@ namespace Umbraco.Core.Components { using (_logger.DebugDuration($"Initializing. (log components when >{LogThresholdMilliseconds}ms)", "Initialized.")) { - foreach (var component in this.Reverse()) // terminate components in reverse order + foreach (var component in this) { var componentType = component.GetType(); using (_logger.DebugDuration($"Initializing {componentType.FullName}.", $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) diff --git a/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs b/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs similarity index 82% rename from src/Umbraco.Core/Components/ComponentCollectionBuilder.cs rename to src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs index 584de7a8f2..47968d3568 100644 --- a/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs +++ b/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Builds a . @@ -31,7 +30,7 @@ namespace Umbraco.Core.Components protected override IComponent CreateItem(IFactory factory, Type itemType) { - using (_logger.DebugDuration($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + using (_logger.DebugDuration($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) { return base.CreateItem(factory, itemType); } diff --git a/src/Umbraco.Core/Components/ComponentComposer.cs b/src/Umbraco.Core/Composing/ComponentComposer.cs similarity index 91% rename from src/Umbraco.Core/Components/ComponentComposer.cs rename to src/Umbraco.Core/Composing/ComponentComposer.cs index 792790c42f..b93ec49a89 100644 --- a/src/Umbraco.Core/Components/ComponentComposer.cs +++ b/src/Umbraco.Core/Composing/ComponentComposer.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Components +using Umbraco.Core.Compose; + +namespace Umbraco.Core.Composing { /// /// Provides a base class for composers which compose a component. diff --git a/src/Umbraco.Core/Components/ComposeAfterAttribute.cs b/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs similarity index 98% rename from src/Umbraco.Core/Components/ComposeAfterAttribute.cs rename to src/Umbraco.Core/Composing/ComposeAfterAttribute.cs index a8fdfaa92b..95c39c7094 100644 --- a/src/Umbraco.Core/Components/ComposeAfterAttribute.cs +++ b/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Indicates that a composer requires another composer. diff --git a/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs b/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs similarity index 98% rename from src/Umbraco.Core/Components/ComposeBeforeAttribute.cs rename to src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs index 17065d1676..f88445b0e7 100644 --- a/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs +++ b/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Indicates that a component is required by another composer. diff --git a/src/Umbraco.Core/Components/Composers.cs b/src/Umbraco.Core/Composing/Composers.cs similarity index 90% rename from src/Umbraco.Core/Components/Composers.cs rename to src/Umbraco.Core/Composing/Composers.cs index 89deed934e..14cb0dce8e 100644 --- a/src/Umbraco.Core/Components/Composers.cs +++ b/src/Umbraco.Core/Composing/Composers.cs @@ -6,7 +6,7 @@ using System.Text; using Umbraco.Core.Collections; using Umbraco.Core.Logging; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { // note: this class is NOT thread-safe in any ways @@ -173,27 +173,46 @@ namespace Umbraco.Core.Components // what happens in case of conflicting remote declarations is unspecified. more // precisely, the last declaration to be processed wins, but the order of the // declarations depends on the type finder and is unspecified. + + void UpdateEnableInfo(Type composerType, int weight2, Dictionary enabled2, bool value) + { + if (enabled.TryGetValue(composerType, out var enableInfo) == false) enableInfo = enabled2[composerType] = new EnableInfo(); + if (enableInfo.Weight > weight2) return; + + enableInfo.Enabled = value; + enableInfo.Weight = weight2; + } + + var assemblies = types.Select(x => x.Assembly).Distinct(); + foreach (var assembly in assemblies) + { + foreach (var attr in assembly.GetCustomAttributes()) + { + var type = attr.EnabledType; + UpdateEnableInfo(type, 2, enabled, true); + } + + foreach (var attr in assembly.GetCustomAttributes()) + { + var type = attr.DisabledType; + UpdateEnableInfo(type, 2, enabled, false); + } + } + foreach (var composerType in types) { foreach (var attr in composerType.GetCustomAttributes()) { var type = attr.EnabledType ?? composerType; - if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); - var weight = type == composerType ? 1 : 2; - if (enableInfo.Weight > weight) continue; - - enableInfo.Enabled = true; - enableInfo.Weight = weight; + var weight = type == composerType ? 1 : 3; + UpdateEnableInfo(type, weight, enabled, true); } + foreach (var attr in composerType.GetCustomAttributes()) { var type = attr.DisabledType ?? composerType; - if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); - var weight = type == composerType ? 1 : 2; - if (enableInfo.Weight > weight) continue; - - enableInfo.Enabled = false; - enableInfo.Weight = weight; + var weight = type == composerType ? 1 : 3; + UpdateEnableInfo(type, weight, enabled, false); } } diff --git a/src/Umbraco.Core/Components/Composition.cs b/src/Umbraco.Core/Composing/Composition.cs similarity index 99% rename from src/Umbraco.Core/Components/Composition.cs rename to src/Umbraco.Core/Composing/Composition.cs index 6df86d793f..d7686ccd07 100644 --- a/src/Umbraco.Core/Components/Composition.cs +++ b/src/Umbraco.Core/Composing/Composition.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Represents a composition. diff --git a/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Configuration.cs similarity index 75% rename from src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs rename to src/Umbraco.Core/Composing/CompositionExtensions/Configuration.cs index 7fba47a2cd..78475953b7 100644 --- a/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Configuration.cs @@ -1,13 +1,11 @@ -using Umbraco.Core.Components; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Configuration.UmbracoSettings; -namespace Umbraco.Core.Composing.Composers +namespace Umbraco.Core.Composing.CompositionExtensions { /// /// Compose configurations. /// - public static class ConfigurationComposer + public static class Configuration { public static Composition ComposeConfiguration(this Composition composition) { diff --git a/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs similarity index 69% rename from src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs rename to src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs index 0274b8f1a9..ed875ec973 100644 --- a/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs @@ -1,11 +1,10 @@ using AutoMapper; -using Umbraco.Core.Components; using Umbraco.Core.Models.Identity; -namespace Umbraco.Core.Composing.Composers +namespace Umbraco.Core.Composing.CompositionExtensions { - public static class CoreMappingProfilesComposer + public static class CoreMappingProfiles { public static Composition ComposeCoreMappingProfiles(this Composition composition) { diff --git a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs b/src/Umbraco.Core/Composing/CompositionExtensions/FileSystems.cs similarity index 92% rename from src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs rename to src/Umbraco.Core/Composing/CompositionExtensions/FileSystems.cs index 4c598f27e4..078a505be9 100644 --- a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/FileSystems.cs @@ -1,10 +1,10 @@ -using Umbraco.Core.Components; +using Umbraco.Core.Compose; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; -namespace Umbraco.Core.Composing.Composers +namespace Umbraco.Core.Composing.CompositionExtensions { - public static class FileSystemsComposer + public static class FileSystems { /* * HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM @@ -73,13 +73,13 @@ namespace Umbraco.Core.Composing.Composers // it needs to be registered (not only the interface) because it provides additional // functionality eg for scoping, and is injected in the scope provider - whereas the // interface is really for end-users to get access to filesystems. - composition.RegisterUnique(factory => factory.CreateInstance(factory)); + composition.RegisterUnique(factory => factory.CreateInstance(factory)); // register IFileSystems, which gives access too all filesystems - composition.RegisterUnique(factory => factory.GetInstance()); + composition.RegisterUnique(factory => factory.GetInstance()); // register the scheme for media paths - composition.RegisterUnique(); + composition.RegisterUnique(); // register the IMediaFileSystem implementation composition.RegisterFileSystem(); diff --git a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs similarity index 95% rename from src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs rename to src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs index 62b92081c1..03f13f96cb 100644 --- a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs @@ -1,13 +1,12 @@ -using Umbraco.Core.Components; -using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; -namespace Umbraco.Core.Composing.Composers +namespace Umbraco.Core.Composing.CompositionExtensions { /// /// Composes repositories. /// - public static class RepositoriesComposer + public static class Repositories { public static Composition ComposeRepositories(this Composition composition) { diff --git a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs similarity index 97% rename from src/Umbraco.Core/Composing/Composers/ServicesComposer.cs rename to src/Umbraco.Core/Composing/CompositionExtensions/Services.cs index 0410e72148..ead2524d80 100644 --- a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -10,9 +9,9 @@ using Umbraco.Core.Packaging; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Composing.Composers +namespace Umbraco.Core.Composing.CompositionExtensions { - public static class ServicesComposer + public static class Services { public static Composition ComposeServices(this Composition composition) { @@ -37,6 +36,7 @@ namespace Umbraco.Core.Composing.Composers composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.Core/Components/DisableAttribute.cs b/src/Umbraco.Core/Composing/DisableAttribute.cs similarity index 77% rename from src/Umbraco.Core/Components/DisableAttribute.cs rename to src/Umbraco.Core/Composing/DisableAttribute.cs index f9a7249b89..c2a419f8bc 100644 --- a/src/Umbraco.Core/Components/DisableAttribute.cs +++ b/src/Umbraco.Core/Composing/DisableAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Indicates that a composer should be disabled. @@ -9,9 +9,8 @@ namespace Umbraco.Core.Components /// If a type is specified, disables the composer of that type, else disables the composer marked with the attribute. /// This attribute is *not* inherited. /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority - /// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from - /// another composer. Anything else is unspecified. + /// Assembly-level has greater priority than + /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] public class DisableAttribute : Attribute diff --git a/src/Umbraco.Core/Composing/DisableComposerAttribute.cs b/src/Umbraco.Core/Composing/DisableComposerAttribute.cs new file mode 100644 index 0000000000..7adc5673ec --- /dev/null +++ b/src/Umbraco.Core/Composing/DisableComposerAttribute.cs @@ -0,0 +1,28 @@ +using System; + +namespace Umbraco.Core.Composing +{ + /// + /// Indicates that a composer should be disabled. + /// + /// + /// Assembly-level has greater priority than + /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] + public class DisableComposerAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public DisableComposerAttribute(Type disabledType) + { + DisabledType = disabledType; + } + + /// + /// Gets the disabled type, or null if it is the composer marked with the attribute. + /// + public Type DisabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/EnableAttribute.cs b/src/Umbraco.Core/Composing/EnableAttribute.cs similarity index 77% rename from src/Umbraco.Core/Components/EnableAttribute.cs rename to src/Umbraco.Core/Composing/EnableAttribute.cs index edf3cbdc2e..276a04cbec 100644 --- a/src/Umbraco.Core/Components/EnableAttribute.cs +++ b/src/Umbraco.Core/Composing/EnableAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Indicates that a composer should be enabled. @@ -9,9 +9,8 @@ namespace Umbraco.Core.Components /// If a type is specified, enables the composer of that type, else enables the composer marked with the attribute. /// This attribute is *not* inherited. /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority - /// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from - /// another composer. Anything else is unspecified. + /// Assembly-level has greater priority than + /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] public class EnableAttribute : Attribute diff --git a/src/Umbraco.Core/Composing/EnableComposerAttribute.cs b/src/Umbraco.Core/Composing/EnableComposerAttribute.cs new file mode 100644 index 0000000000..a1bc0a8123 --- /dev/null +++ b/src/Umbraco.Core/Composing/EnableComposerAttribute.cs @@ -0,0 +1,31 @@ +using System; + +namespace Umbraco.Core.Composing +{ + /// + /// Indicates that a composer should be enabled. + /// + /// + /// If a type is specified, enables the composer of that type, else enables the composer marked with the attribute. + /// This attribute is *not* inherited. + /// This attribute applies to classes only, it is not possible to enable/disable interfaces. + /// Assembly-level has greater priority than + /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] + public class EnableComposerAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public EnableComposerAttribute(Type enabledType) + { + EnabledType = enabledType; + } + + /// + /// Gets the enabled type, or null if it is the composer marked with the attribute. + /// + public Type EnabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/IComponent.cs b/src/Umbraco.Core/Composing/IComponent.cs similarity index 95% rename from src/Umbraco.Core/Components/IComponent.cs rename to src/Umbraco.Core/Composing/IComponent.cs index b1954d821b..dcd0365b12 100644 --- a/src/Umbraco.Core/Components/IComponent.cs +++ b/src/Umbraco.Core/Composing/IComponent.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Represents a component. diff --git a/src/Umbraco.Core/Components/IComposer.cs b/src/Umbraco.Core/Composing/IComposer.cs similarity index 67% rename from src/Umbraco.Core/Components/IComposer.cs rename to src/Umbraco.Core/Composing/IComposer.cs index ce02aa4f13..b73a745b61 100644 --- a/src/Umbraco.Core/Components/IComposer.cs +++ b/src/Umbraco.Core/Composing/IComposer.cs @@ -1,6 +1,4 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Represents a composer. @@ -10,7 +8,6 @@ namespace Umbraco.Core.Components /// /// Compose. /// - /// void Compose(Composition composition); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/ICoreComposer.cs b/src/Umbraco.Core/Composing/ICoreComposer.cs new file mode 100644 index 0000000000..1e9e5fced5 --- /dev/null +++ b/src/Umbraco.Core/Composing/ICoreComposer.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Represents a core . + /// + /// + /// Core composers compose after the initial composer, and before user composers. + /// + public interface ICoreComposer : IComposer + { } +} diff --git a/src/Umbraco.Core/Components/IUserComposer.cs b/src/Umbraco.Core/Composing/IUserComposer.cs similarity index 62% rename from src/Umbraco.Core/Components/IUserComposer.cs rename to src/Umbraco.Core/Composing/IUserComposer.cs index 59e0023635..96f6b38189 100644 --- a/src/Umbraco.Core/Components/IUserComposer.cs +++ b/src/Umbraco.Core/Composing/IUserComposer.cs @@ -1,12 +1,12 @@ -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Represents a user . /// /// - /// All user composers require (compose after) all core composers. + /// User composers compose after core composers, and before the final composer. /// [ComposeAfter(typeof(ICoreComposer))] public interface IUserComposer : IComposer { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs index 241b84d8d2..2bd4c0d434 100644 --- a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs @@ -78,33 +78,6 @@ namespace Umbraco.Core.Composing return This; } - /// - /// Appends a type after another type. - /// - /// The other type. - /// The type to append. - /// The builder. - /// Throws if both types are identical, or if the other type does not already belong to the collection. - public TBuilder AppendAfter() - where TAfter : TItem - where T : TItem - { - Configure(types => - { - var typeAfter = typeof (TAfter); - var type = typeof(T); - if (typeAfter == type) throw new InvalidOperationException(); - - var index = types.IndexOf(typeAfter); - if (index < 0) throw new InvalidOperationException(); - - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeAfter); // in case removing type changed index - types.Insert(index + 1, type); - }); - return This; - } - /// /// Inserts a type into the collection. /// @@ -206,6 +179,69 @@ namespace Umbraco.Core.Composing return This; } + /// + /// Inserts a type after another type. + /// + /// The other type. + /// The type to append. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertAfter() + where TAfter : TItem + where T : TItem + { + Configure(types => + { + var typeAfter = typeof(TAfter); + var type = typeof(T); + if (typeAfter == type) throw new InvalidOperationException(); + + var index = types.IndexOf(typeAfter); + if (index < 0) throw new InvalidOperationException(); + + if (types.Contains(type)) types.Remove(type); + index = types.IndexOf(typeAfter); // in case removing type changed index + index += 1; // insert here + + if (index == types.Count) + types.Add(type); + else + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type after another type. + /// + /// The other type. + /// The type to insert. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertAfter(Type typeAfter, Type type) + { + Configure(types => + { + EnsureType(typeAfter, "find"); + EnsureType(type, "register"); + + if (typeAfter == type) throw new InvalidOperationException(); + + var index = types.IndexOf(typeAfter); + if (index < 0) throw new InvalidOperationException(); + + if (types.Contains(type)) types.Remove(type); + index = types.IndexOf(typeAfter); // in case removing type changed index + index += 1; // insert here + + if (index == types.Count) + types.Add(type); + else + types.Insert(index, type); + }); + return This; + } + /// /// Removes a type from the collection. /// diff --git a/src/Umbraco.Core/Composing/RegisterFactory.cs b/src/Umbraco.Core/Composing/RegisterFactory.cs index 8ee6e5a94c..ea25d6a135 100644 --- a/src/Umbraco.Core/Composing/RegisterFactory.cs +++ b/src/Umbraco.Core/Composing/RegisterFactory.cs @@ -18,14 +18,14 @@ namespace Umbraco.Core.Composing /// Creates a new instance of the configured container. /// /// - /// To override the default LightInjectContainer, add an appSetting named umbracoContainerType with + /// To override the default LightInjectContainer, add an appSetting named 'Umbraco.Core.RegisterType' with /// a fully qualified type name to a class with a static method "Create" returning an IRegister. /// public static IRegister Create() { Type type; - var configuredTypeName = ConfigurationManager.AppSettings["umbracoRegisterType"]; + var configuredTypeName = ConfigurationManager.AppSettings[Constants.AppSettings.RegisterType]; if (configuredTypeName.IsNullOrWhiteSpace()) { // try to get the web LightInject container type, diff --git a/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs b/src/Umbraco.Core/Composing/RuntimeLevelAttribute.cs similarity index 92% rename from src/Umbraco.Core/Components/RuntimeLevelAttribute.cs rename to src/Umbraco.Core/Composing/RuntimeLevelAttribute.cs index 2c698a671a..5de90ee8e8 100644 --- a/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs +++ b/src/Umbraco.Core/Composing/RuntimeLevelAttribute.cs @@ -1,7 +1,6 @@ using System; -using Umbraco.Core.Composing; -namespace Umbraco.Core.Components +namespace Umbraco.Core.Composing { /// /// Marks a composer to indicate a minimum and/or maximum runtime level for which the composer would compose. diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs new file mode 100644 index 0000000000..35f85c9c2f --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a base class for collections of types. + /// + public abstract class TypeCollectionBuilderBase : ICollectionBuilder + where TCollection : class, IBuilderCollection + { + private readonly HashSet _types = new HashSet(); + + private Type Validate(Type type, string action) + { + if (!typeof(TConstraint).IsAssignableFrom(type)) + throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}."); + return type; + } + + public void Add(Type type) => _types.Add(Validate(type, "add")); + + public void Add() => Add(typeof(T)); + + public void Add(IEnumerable types) + { + foreach (var type in types) Add(type); + } + + public void Remove(Type type) => _types.Remove(Validate(type, "remove")); + + public void Remove() => Remove(typeof(T)); + + public TCollection CreateCollection(IFactory factory) + { + return factory.CreateInstance(_types); + } + + public void RegisterWith(IRegister register) + { + register.Register(CreateCollection, Lifetime.Singleton); + } + } +} diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 308d0ecfd7..5ad1e43580 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Composing if (_assembliesAcceptingLoadExceptions != null) return _assembliesAcceptingLoadExceptions; - var s = ConfigurationManager.AppSettings["Umbraco.AssembliesAcceptingLoadExceptions"]; + var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions]; return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) ? Array.Empty() : s.Split(',').Select(x => x.Trim()).ToArray(); diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index a2d6349ea0..af9277fce9 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -42,30 +42,30 @@ namespace Umbraco.Core.Composing private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; - private static LocalTempStorage _localTempStorage; + private static string _localTempPath; private static string _fileBasePath; /// /// Initializes a new instance of the class. /// /// The application runtime cache. - /// Files storage mode. + /// Files storage location. /// A profiling logger. - public TypeLoader(IAppPolicyCache runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger) - : this(runtimeCache, localTempStorage, logger, true) + public TypeLoader(IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger) + : this(runtimeCache, localTempPath, logger, true) { } /// /// Initializes a new instance of the class. /// /// The application runtime cache. - /// Files storage mode. + /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(IAppPolicyCache runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger, bool detectChanges) + internal TypeLoader(IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges) { _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); - _localTempStorage = localTempStorage == LocalTempStorage.Unknown ? LocalTempStorage.Default : localTempStorage; + _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (detectChanges) @@ -211,7 +211,7 @@ namespace Umbraco.Core.Composing /// file properties (false) or the file contents (true). private static string GetFileHash(IEnumerable> filesAndFolders, IProfilingLogger logger) { - using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) + using (logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) { // get the distinct file infos to hash var uniqInfos = new HashSet(); @@ -269,7 +269,7 @@ namespace Umbraco.Core.Composing // internal for tests internal static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) { - using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) + using (logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) { using (var generator = new HashGenerator()) { @@ -388,25 +388,7 @@ namespace Umbraco.Core.Composing if (_fileBasePath != null) return _fileBasePath; - switch (_localTempStorage) - { - case LocalTempStorage.AspNetTemp: - _fileBasePath = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types"); - break; - case LocalTempStorage.EnvironmentTemp: - // include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back - // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not - // utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId - var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); - var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); - _fileBasePath = Path.Combine(cachePath, "umbraco-types"); - break; - case LocalTempStorage.Default: - default: - var tempFolder = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "TypesCache"); - _fileBasePath = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName); - break; - } + _fileBasePath = Path.Combine(_localTempPath, "TypesCache", "umbraco-types." + NetworkHelper.FileSafeMachineName); // ensure that the folder exists var directory = Path.GetDirectoryName(_fileBasePath); @@ -666,7 +648,7 @@ namespace Umbraco.Core.Composing var name = GetName(baseType, attributeType); lock (_locko) - using (_logger.TraceDuration( + using (_logger.DebugDuration( "Getting " + name, "Got " + name)) // cannot contain typesFound.Count as it's evaluated before the find { diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs similarity index 84% rename from src/Umbraco.Core/Components/CompositionExtensions.cs rename to src/Umbraco.Core/CompositionExtensions.cs index 8810213421..e1c7ad4467 100644 --- a/src/Umbraco.Core/Components/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -1,64 +1,23 @@ using System; using Umbraco.Core.Cache; -using Umbraco.Core.Dictionary; using Umbraco.Core.Composing; +using Umbraco.Core.Dictionary; using Umbraco.Core.IO; -using Umbraco.Core.Migrations; +using Umbraco.Core.Logging.Viewer; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PackageActions; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; using Umbraco.Core.Sync; -using Umbraco.Core.Logging.Viewer; -using Umbraco.Core.PackageActions; -namespace Umbraco.Core.Components +namespace Umbraco.Core { /// /// Provides extension methods to the class. /// - public static class CompositionExtensions + public static partial class CompositionExtensions { - #region FileSystems - - /// - /// Registers a filesystem. - /// - /// The type of the filesystem. - /// The implementing type. - /// The composition. - /// The register. - public static void RegisterFileSystem(this Composition composition) - where TImplementing : FileSystemWrapper, TFileSystem - where TFileSystem : class - { - composition.RegisterUnique(factory => - { - var fileSystems = factory.GetInstance(); - var supporting = factory.GetInstance(); - return fileSystems.GetFileSystem(supporting.For()); - }); - } - - /// - /// Registers a filesystem. - /// - /// The type of the filesystem. - /// The composition. - /// The register. - public static void RegisterFileSystem(this Composition composition) - where TFileSystem : FileSystemWrapper - { - composition.RegisterUnique(factory => - { - var fileSystems = factory.GetInstance(); - var supporting = factory.GetInstance(); - return fileSystems.GetFileSystem(supporting.For()); - }); - } - - #endregion - #region Collection Builders /// @@ -110,13 +69,6 @@ namespace Umbraco.Core.Components internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition) => composition.WithCollectionBuilder(); - /// - /// Gets the post-migrations collection builder. - /// - /// The composition. - internal static PostMigrationCollectionBuilder PostMigrations(this Composition composition) - => composition.WithCollectionBuilder(); - /// /// Gets the components collection builder. /// diff --git a/src/Umbraco.Core/CompositionExtensions_Essentials.cs b/src/Umbraco.Core/CompositionExtensions_Essentials.cs new file mode 100644 index 0000000000..b85479716c --- /dev/null +++ b/src/Umbraco.Core/CompositionExtensions_Essentials.cs @@ -0,0 +1,35 @@ +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods to the class. + /// + public static partial class CompositionExtensions + { + /// + /// Registers essential services. + /// + public static void RegisterEssentials(this Composition composition, + ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger, + IMainDom mainDom, + AppCaches appCaches, + IUmbracoDatabaseFactory databaseFactory, + TypeLoader typeLoader, + IRuntimeState state) + { + composition.RegisterUnique(logger); + composition.RegisterUnique(profiler); + composition.RegisterUnique(profilingLogger); + composition.RegisterUnique(mainDom); + composition.RegisterUnique(appCaches); + composition.RegisterUnique(databaseFactory); + composition.RegisterUnique(factory => factory.GetInstance().SqlContext); + composition.RegisterUnique(typeLoader); + composition.RegisterUnique(state); + } + } +} diff --git a/src/Umbraco.Core/CompositionExtensions_FileSystems.cs b/src/Umbraco.Core/CompositionExtensions_FileSystems.cs new file mode 100644 index 0000000000..f6c65e1e1f --- /dev/null +++ b/src/Umbraco.Core/CompositionExtensions_FileSystems.cs @@ -0,0 +1,47 @@ +using Umbraco.Core.Composing; +using Umbraco.Core.IO; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods to the class. + /// + public static partial class CompositionExtensions + { + /// + /// Registers a filesystem. + /// + /// The type of the filesystem. + /// The implementing type. + /// The composition. + /// The register. + public static void RegisterFileSystem(this Composition composition) + where TImplementing : FileSystemWrapper, TFileSystem + where TFileSystem : class + { + composition.RegisterUnique(factory => + { + var fileSystems = factory.GetInstance(); + var supporting = factory.GetInstance(); + return fileSystems.GetFileSystem(supporting.For()); + }); + } + + /// + /// Registers a filesystem. + /// + /// The type of the filesystem. + /// The composition. + /// The register. + public static void RegisterFileSystem(this Composition composition) + where TFileSystem : FileSystemWrapper + { + composition.RegisterUnique(factory => + { + var fileSystems = factory.GetInstance(); + var supporting = factory.GetInstance(); + return fileSystems.GetFileSystem(supporting.For()); + }); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions_Uniques.cs similarity index 51% rename from src/Umbraco.Core/Composing/CompositionExtensions.cs rename to src/Umbraco.Core/CompositionExtensions_Uniques.cs index c312259b82..52f1ce7ecd 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions_Uniques.cs @@ -1,43 +1,12 @@ -using Umbraco.Core.Cache; -using Umbraco.Core.Components; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; +using Umbraco.Core.Composing; -namespace Umbraco.Core.Composing +namespace Umbraco.Core { /// /// Provides extension methods to the class. /// - public static class CompositionExtensions + public static partial class CompositionExtensions { - #region Essentials - - /// - /// Registers essential services. - /// - public static void RegisterEssentials(this Composition composition, - ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger, - IMainDom mainDom, - AppCaches appCaches, - IUmbracoDatabaseFactory databaseFactory, - TypeLoader typeLoader, - IRuntimeState state) - { - composition.RegisterUnique(logger); - composition.RegisterUnique(profiler); - composition.RegisterUnique(profilingLogger); - composition.RegisterUnique(mainDom); - composition.RegisterUnique(appCaches); - composition.RegisterUnique(databaseFactory); - composition.RegisterUnique(factory => factory.GetInstance().SqlContext); - composition.RegisterUnique(typeLoader); - composition.RegisterUnique(state); - } - - #endregion - - #region Unique - /// /// Registers a unique service as its own implementation. /// @@ -62,7 +31,5 @@ namespace Umbraco.Core.Composing /// public static void RegisterUnique(this Composition composition, TService instance) => composition.RegisterUnique(typeof(TService), instance); - - #endregion } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/CoreDebug.cs b/src/Umbraco.Core/Configuration/CoreDebug.cs index 3e39eb6db5..b24e8a3329 100644 --- a/src/Umbraco.Core/Configuration/CoreDebug.cs +++ b/src/Umbraco.Core/Configuration/CoreDebug.cs @@ -7,8 +7,8 @@ namespace Umbraco.Core.Configuration public CoreDebug() { var appSettings = System.Configuration.ConfigurationManager.AppSettings; - LogUncompletedScopes = string.Equals("true", appSettings["Umbraco.CoreDebug.LogUncompletedScopes"], StringComparison.OrdinalIgnoreCase); - DumpOnTimeoutThreadAbort = string.Equals("true", appSettings["Umbraco.CoreDebug.DumpOnTimeoutThreadAbort"], StringComparison.OrdinalIgnoreCase); + LogUncompletedScopes = string.Equals("true", appSettings[Constants.AppSettings.Debug.LogUncompletedScopes], StringComparison.OrdinalIgnoreCase); + DumpOnTimeoutThreadAbort = string.Equals("true", appSettings[Constants.AppSettings.Debug.DumpOnTimeoutThreadAbort], StringComparison.OrdinalIgnoreCase); } // when true, Scope logs the stack trace for any scope that gets disposed without being completed. diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index b89a4d1daf..09962fab5a 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -4,15 +4,8 @@ using System.Linq; using System.Net.Configuration; using System.Web; using System.Web.Configuration; -using System.Web.Hosting; -using System.Web.Security; -using System.Xml; using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core.Composing; using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Security; namespace Umbraco.Core.Configuration { @@ -31,7 +24,7 @@ namespace Umbraco.Core.Configuration private static string _reservedPaths; private static string _reservedUrls; //ensure the built on (non-changeable) reserved paths are there at all times - internal const string StaticReservedPaths = "~/app_plugins/,~/install/,"; //must end with a comma! + internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! internal const string StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma! #endregion @@ -85,8 +78,8 @@ namespace Umbraco.Core.Configuration { if (_reservedUrls != null) return _reservedUrls; - var urls = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedUrls") - ? ConfigurationManager.AppSettings["umbracoReservedUrls"] + var urls = ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ReservedUrls) + ? ConfigurationManager.AppSettings[Constants.AppSettings.ReservedUrls] : string.Empty; //ensure the built on (non-changeable) reserved paths are there at all times @@ -107,14 +100,14 @@ namespace Umbraco.Core.Configuration if (_reservedPaths != null) return _reservedPaths; var reservedPaths = StaticReservedPaths; - var umbPath = ConfigurationManager.AppSettings.ContainsKey("umbracoPath") && !ConfigurationManager.AppSettings["umbracoPath"].IsNullOrWhiteSpace() - ? ConfigurationManager.AppSettings["umbracoPath"] + var umbPath = ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.Path) && !ConfigurationManager.AppSettings[Constants.AppSettings.Path].IsNullOrWhiteSpace() + ? ConfigurationManager.AppSettings[Constants.AppSettings.Path] : "~/umbraco"; //always add the umbraco path to the list reservedPaths += umbPath.EnsureEndsWith(','); - var allPaths = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedPaths") - ? ConfigurationManager.AppSettings["umbracoReservedPaths"] + var allPaths = ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ReservedPaths) + ? ConfigurationManager.AppSettings[Constants.AppSettings.ReservedPaths] : string.Empty; _reservedPaths = reservedPaths + allPaths; @@ -133,8 +126,8 @@ namespace Umbraco.Core.Configuration { get { - return ConfigurationManager.AppSettings.ContainsKey("umbracoContentXML") - ? ConfigurationManager.AppSettings["umbracoContentXML"] + return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ContentXML) + ? ConfigurationManager.AppSettings[Constants.AppSettings.ContentXML] : "~/App_Data/umbraco.config"; } } @@ -147,8 +140,8 @@ namespace Umbraco.Core.Configuration { get { - return ConfigurationManager.AppSettings.ContainsKey("umbracoPath") - ? IOHelper.ResolveUrl(ConfigurationManager.AppSettings["umbracoPath"]) + return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.Path) + ? IOHelper.ResolveUrl(ConfigurationManager.AppSettings[Constants.AppSettings.Path]) : string.Empty; } } @@ -161,13 +154,13 @@ namespace Umbraco.Core.Configuration { get { - return ConfigurationManager.AppSettings.ContainsKey("umbracoConfigurationStatus") - ? ConfigurationManager.AppSettings["umbracoConfigurationStatus"] + return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ConfigurationStatus) + ? ConfigurationManager.AppSettings[Constants.AppSettings.ConfigurationStatus] : string.Empty; } set { - SaveSetting("umbracoConfigurationStatus", value); + SaveSetting(Constants.AppSettings.ConfigurationStatus, value); } } @@ -249,7 +242,7 @@ namespace Umbraco.Core.Configuration { try { - return int.Parse(ConfigurationManager.AppSettings["umbracoTimeOutInMinutes"]); + return int.Parse(ConfigurationManager.AppSettings[Constants.AppSettings.TimeOutInMinutes]); } catch { @@ -268,7 +261,7 @@ namespace Umbraco.Core.Configuration { try { - return int.Parse(ConfigurationManager.AppSettings["umbracoVersionCheckPeriod"]); + return int.Parse(ConfigurationManager.AppSettings[Constants.AppSettings.VersionCheckPeriod]); } catch { @@ -277,17 +270,12 @@ namespace Umbraco.Core.Configuration } } - /// - /// This is the location type to store temporary files such as cache files or other localized files for a given machine - /// - /// - /// Currently used for the xml cache file and the plugin cache files - /// + /// public LocalTempStorage LocalTempStorageLocation { get { - var setting = ConfigurationManager.AppSettings["umbracoLocalTempStorage"]; + var setting = ConfigurationManager.AppSettings[Constants.AppSettings.LocalTempStorage]; if (!string.IsNullOrWhiteSpace(setting)) return Enum.Parse(setting); @@ -295,6 +283,30 @@ namespace Umbraco.Core.Configuration } } + /// + public string LocalTempPath + { + get + { + switch (LocalTempStorageLocation) + { + case LocalTempStorage.AspNetTemp: + return System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData"); + case LocalTempStorage.EnvironmentTemp: + // include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back + // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not + // utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId + var appDomainHash = HttpRuntime.AppDomainAppId.GenerateHash(); + return System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); + //case LocalTempStorage.Default: + //case LocalTempStorage.Unknown: + default: + return IOHelper.MapPath("~/App_Data/TEMP"); + } + } + } + + /// /// Gets the default UI language. /// @@ -304,8 +316,8 @@ namespace Umbraco.Core.Configuration { get { - return ConfigurationManager.AppSettings.ContainsKey("umbracoDefaultUILanguage") - ? ConfigurationManager.AppSettings["umbracoDefaultUILanguage"] + return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.DefaultUILanguage) + ? ConfigurationManager.AppSettings[Constants.AppSettings.DefaultUILanguage] : string.Empty; } } @@ -322,7 +334,7 @@ namespace Umbraco.Core.Configuration { try { - return bool.Parse(ConfigurationManager.AppSettings["umbracoHideTopLevelNodeFromPath"]); + return bool.Parse(ConfigurationManager.AppSettings[Constants.AppSettings.HideTopLevelNodeFromPath]); } catch { @@ -340,7 +352,7 @@ namespace Umbraco.Core.Configuration { try { - return bool.Parse(ConfigurationManager.AppSettings["umbracoUseHttps"]); + return bool.Parse(ConfigurationManager.AppSettings[Constants.AppSettings.UseHttps]); } catch { @@ -348,10 +360,5 @@ namespace Umbraco.Core.Configuration } } } - } - - - - } diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index 1e37ae0668..6bfb7ea904 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -128,6 +128,7 @@ namespace Umbraco.Core.Configuration if (routes == null) throw new ArgumentNullException(nameof(routes)); //check if the current request matches a route, if so then it is reserved. + //TODO: This value should be cached! Else this is doing double routing in MVC every request! var route = routes.GetRouteData(httpContext); if (route != null) return true; diff --git a/src/Umbraco.Core/Configuration/IGlobalSettings.cs b/src/Umbraco.Core/Configuration/IGlobalSettings.cs index b3c8ded1c0..b96434c30c 100644 --- a/src/Umbraco.Core/Configuration/IGlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/IGlobalSettings.cs @@ -1,6 +1,4 @@ -using System; - -namespace Umbraco.Core.Configuration +namespace Umbraco.Core.Configuration { /// /// Contains general settings information for the entire Umbraco instance based on information from web.config appsettings @@ -61,11 +59,13 @@ namespace Umbraco.Core.Configuration int VersionCheckPeriod { get; } /// - /// This is the location type to store temporary files such as cache files or other localized files for a given machine + /// Gets the configuration for the location of temporary files. /// - /// - /// Used for some cache files and for specific environments such as Azure - /// LocalTempStorage LocalTempStorageLocation { get; } + + /// + /// Gets the location of temporary files. + /// + string LocalTempPath { get; } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 7ab97500f0..2f615d26b3 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -82,7 +82,7 @@ namespace Umbraco.Core.Configuration try { // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings - var value = ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; + var value = ConfigurationManager.AppSettings[Constants.AppSettings.ConfigurationStatus]; return value.IsNullOrWhiteSpace() ? null : SemVersion.TryParse(value, out var semver) ? semver : null; } catch diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs new file mode 100644 index 0000000000..ac0cbde8de --- /dev/null +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -0,0 +1,124 @@ +using System; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Specific web.config AppSetting keys for Umbraco.Core application + /// + public static class AppSettings + { + // TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27 + [Obsolete("We need to kill this appsetting as we do not use XML content cache umbraco.config anymore due to NuCache")] + public const string ContentXML = "Umbraco.Core.ContentXML"; //umbracoContentXML + + /// + /// TODO: FILL ME IN + /// + public const string RegisterType = "Umbraco.Core.RegisterType"; + + /// + /// This is used for a unit test in PublishedMediaCache + /// + public const string PublishedMediaCacheSeconds = "Umbraco.Core.PublishedMediaCacheSeconds"; + + /// + /// TODO: FILL ME IN + /// + public const string AssembliesAcceptingLoadExceptions = "Umbraco.Core.AssembliesAcceptingLoadExceptions"; + + /// + /// This will return the version number of the currently installed umbraco instance + /// + /// + /// Umbraco will automatically set & modify this value when installing & upgrading + /// + public const string ConfigurationStatus = "Umbraco.Core.ConfigurationStatus"; + + /// + /// Gets the path to umbraco's root directory (/umbraco by default). + /// + public const string Path = "Umbraco.Core.Path"; + + /// + /// The reserved urls from web.config. + /// + public const string ReservedUrls = "Umbraco.Core.ReservedUrls"; + + /// + /// The reserved paths from web.config + /// + public const string ReservedPaths = "Umbraco.Core.ReservedPaths"; + + /// + /// Set the timeout for the Umbraco backoffice in minutes + /// + public const string TimeOutInMinutes = "Umbraco.Core.TimeOutInMinutes"; + + /// + /// The number of days to check for a new version of Umbraco + /// + /// + /// Default is set to 7. Setting this to 0 will never check + /// This is used to help track statistics + /// + public const string VersionCheckPeriod = "Umbraco.Core.VersionCheckPeriod"; + + /// + /// This is the location type to store temporary files such as cache files or other localized files for a given machine + /// + /// + /// Currently used for the xml cache file and the plugin cache files + /// + public const string LocalTempStorage = "Umbraco.Core.LocalTempStorage"; + + /// + /// The default UI language of the backoffice such as 'en-US' + /// + public const string DefaultUILanguage = "Umbraco.Core.DefaultUILanguage"; + + /// + /// A true/false value indicating whether umbraco should hide top level nodes from generated urls. + /// + public const string HideTopLevelNodeFromPath = "Umbraco.Core.HideTopLevelNodeFromPath"; + + /// + /// A true or false indicating whether umbraco should force a secure (https) connection to the backoffice. + /// + public const string UseHttps = "Umbraco.Core.UseHttps"; + + /// + /// TODO: FILL ME IN + /// + public const string DisableElectionForSingleServer = "Umbraco.Core.DisableElectionForSingleServer"; + + + /// + /// Debug specific web.config AppSetting keys for Umbraco + /// + /// + /// Do not use these keys in a production environment + /// + public static class Debug + { + /// + /// When set to true, Scope logs the stack trace for any scope that gets disposed without being completed. + /// this helps troubleshooting rogue scopes that we forget to complete + /// + public const string LogUncompletedScopes = "Umbraco.Core.Debug.LogUncompletedScopes"; + + /// + /// When set to true, the Logger creates a mini dump of w3wp in ~/App_Data/MiniDump whenever it logs + /// an error due to a ThreadAbortException that is due to a timeout. + /// + public const string DumpOnTimeoutThreadAbort = "Umbraco.Core.Debug.DumpOnTimeoutThreadAbort"; + + /// + /// TODO: FILL ME IN + /// + public const string DatabaseFactoryServerVersion = "Umbraco.Core.Debug.DatabaseFactoryServerVersion"; + } + } + } +} diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 2d4042fad0..b53a2b8eaf 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -77,7 +77,7 @@ /// alias for the macro tree. /// public const string Macros = "macros"; - + /// /// alias for the datatype tree. /// @@ -92,7 +92,7 @@ /// alias for the dictionary tree. /// public const string Dictionary = "dictionary"; - + public const string Stylesheets = "stylesheets"; /// @@ -121,7 +121,7 @@ public const string Templates = "templates"; public const string RelationTypes = "relationTypes"; - + public const string Languages = "languages"; /// diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index b3281e8967..d49244d9f9 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -219,7 +219,7 @@ namespace Umbraco.Core }, { FailedPasswordAttempts, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer, true, FailedPasswordAttempts) + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Integer, true, FailedPasswordAttempts) { Name = FailedPasswordAttemptsLabel } @@ -240,35 +240,35 @@ namespace Umbraco.Core }, { LastLockoutDate, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastLockoutDate) + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLockoutDate) { Name = LastLockoutDateLabel } }, { LastLoginDate, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastLoginDate) + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLoginDate) { Name = LastLoginDateLabel } }, { LastPasswordChangeDate, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastPasswordChangeDate) + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastPasswordChangeDate) { Name = LastPasswordChangeDateLabel } }, { PasswordAnswer, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordAnswer) + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordAnswer) { Name = PasswordAnswerLabel } }, { PasswordQuestion, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordQuestion) + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordQuestion) { Name = PasswordQuestionLabel } diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 8b637fe90e..32f9407616 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -34,11 +34,7 @@ namespace Umbraco.Core /// public const string ContentPicker = "Umbraco.ContentPicker"; - /// - /// Date. - /// - public const string Date = "Umbraco.Date"; - + /// /// DateTime. /// @@ -48,7 +44,7 @@ namespace Umbraco.Core /// DropDown List. /// public const string DropDownListFlexible = "Umbraco.DropDown.Flexible"; - + /// /// Grid. /// @@ -105,9 +101,9 @@ namespace Umbraco.Core public const string MultipleTextstring = "Umbraco.MultipleTextstring"; /// - /// NoEdit. + /// Label. /// - public const string NoEdit = "Umbraco.NoEdit"; + public const string Label = "Umbraco.Label"; /// /// Picker Relations. @@ -118,7 +114,7 @@ namespace Umbraco.Core /// RadioButton list. /// public const string RadioButtonList = "Umbraco.RadioButtonList"; - + /// /// Slider. /// @@ -142,7 +138,7 @@ namespace Umbraco.Core /// /// TinyMCE /// - public const string TinyMce = "Umbraco.TinyMCEv3"; + public const string TinyMce = "Umbraco.TinyMCE"; /// /// Boolean. @@ -163,7 +159,7 @@ namespace Umbraco.Core /// Upload Field. /// public const string UploadField = "Umbraco.UploadField"; - + /// /// Email Address. /// diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index dabd7b4bd5..f802c2d0d9 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -52,36 +52,8 @@ namespace Umbraco.Core return ContentStatus.Unpublished; } - /// - /// Gets the cultures that have been flagged for unpublishing. - /// - /// Gets cultures for which content.UnpublishCulture() has been invoked. - internal static IReadOnlyList GetCulturesUnpublishing(this IContent content) - { - if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos")) - return Array.Empty(); - - var culturesChanging = content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key); - return culturesChanging - .Where(x => !content.IsCulturePublished(x) && // is not published anymore - content.WasCulturePublished(x)) // but was published before - .ToList(); - } - - /// - /// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published) - /// - /// - /// - /// - /// This is helpful for determining if the published event will execute during the saved event for a content item. - /// - internal static bool JustPublished(this IContent entity) - { - var dirty = (IRememberBeingDirty)entity; - return dirty.WasPropertyDirty("Published") && entity.Published; - } - + + #endregion /// @@ -129,7 +101,7 @@ namespace Umbraco.Core } return false; } - + /// /// Returns properties that do not belong to a group /// @@ -137,10 +109,9 @@ namespace Umbraco.Core /// public static IEnumerable GetNonGroupedProperties(this IContentBase content) { - var propertyIdsInTabs = content.PropertyGroups.SelectMany(pg => pg.PropertyTypes); return content.Properties - .Where(property => propertyIdsInTabs.Contains(property.PropertyType) == false) - .OrderBy(x => x.PropertyType.SortOrder); + .Where(x => x.PropertyType.PropertyGroupId == null) + .OrderBy(x => x.PropertyType.SortOrder); } /// @@ -158,16 +129,6 @@ namespace Umbraco.Core .Contains(property.PropertyTypeId)); } - public static IContentTypeComposition GetContentType(this IContentBase contentBase) - { - if (contentBase == null) throw new ArgumentNullException(nameof(contentBase)); - - if (contentBase is IContent content) return content.ContentType; - if (contentBase is IMedia media) return media.ContentType; - if (contentBase is IMember member) return member.ContentType; - throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + "."); - } - #region SetValue for setting file contents /// @@ -176,7 +137,7 @@ namespace Umbraco.Core /// This really is for FileUpload fields only, and should be obsoleted. For anything else, /// you need to store the file by yourself using Store and then figure out /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. - public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) + public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) { if (filename == null || filestream == null) return; @@ -185,12 +146,12 @@ namespace Umbraco.Core if (string.IsNullOrWhiteSpace(filename)) return; filename = filename.ToLower(); - SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment); + SetUploadFile(content,contentTypeBaseServiceProvider, propertyTypeAlias, filename, filestream, culture, segment); } - private static void SetUploadFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) + private static void SetUploadFile(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) { - var property = GetProperty(content, propertyTypeAlias); + var property = GetProperty(content, contentTypeBaseServiceProvider, propertyTypeAlias); // Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an // existing IMedia with extension SetValue causes exception 'Illegal characters in path' @@ -211,12 +172,13 @@ namespace Umbraco.Core } // gets or creates a property for a content item. - private static Property GetProperty(IContentBase content, string propertyTypeAlias) + private static Property GetProperty(IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias) { var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (property != null) return property; - var propertyType = content.GetContentType().CompositionPropertyTypes + var contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); + var propertyType = contentType.CompositionPropertyTypes .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (propertyType == null) throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); @@ -242,9 +204,10 @@ namespace Umbraco.Core /// the "folder number" that was assigned to the previous file referenced by the property, /// if any. /// - public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath) + public static string StoreFile(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string filepath) { - var propertyType = content.GetContentType() + var contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); + var propertyType = contentType .CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); return MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath); @@ -325,7 +288,7 @@ namespace Umbraco.Core { return serializer.Serialize(content, false, false); } - + /// /// Creates the xml representation for the object diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 516192b905..d25997b5f0 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -9,6 +9,16 @@ namespace Umbraco.Core /// public static class ContentVariationExtensions { + /// + /// Determines whether the content type is invariant. + /// + public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing(); + + /// + /// Determines whether the content type varies by culture. + /// + public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); + /// /// Determines whether the content type is invariant. /// diff --git a/src/Umbraco.Core/DisposableObject.cs b/src/Umbraco.Core/DisposableObject.cs deleted file mode 100644 index b2c150f96c..0000000000 --- a/src/Umbraco.Core/DisposableObject.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// Abstract implementation of IDisposable. - /// - /// - /// This is for objects that DO have unmanaged resources. Use - /// for objects that do NOT have unmanaged resources, and avoid creating a finalizer. - /// - /// Can also be used as a pattern for when inheriting is not possible. - /// - /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx - /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ - /// - /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor - /// has allocated disposable objects, it should take care of disposing them. - /// - public abstract class DisposableObject : IDisposable - { - private readonly object _locko = new object(); - - // gets a value indicating whether this instance is disposed. - // for internal tests only (not thread safe) - public bool Disposed { get; private set; } - - // implements IDisposable - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - // finalizer - ~DisposableObject() - { - Dispose(false); - } - - private void Dispose(bool disposing) - { - // can happen if the object construction failed - if (_locko == null) - return; - - lock (_locko) - { - if (Disposed) return; - Disposed = true; - } - - DisposeUnmanagedResources(); - - if (disposing) - DisposeResources(); - } - - protected abstract void DisposeResources(); - - protected virtual void DisposeUnmanagedResources() - { } - } -} diff --git a/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs new file mode 100644 index 0000000000..1d52d0d847 --- /dev/null +++ b/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Events +{ + /// + /// Represents event data, for events that support cancellation, and expose impacted objects. + /// + /// The type of the exposed, impacted objects. + public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable> + { + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject) + : base(eventObject) + { } + + public bool Equals(CancellableEnumerableObjectEventArgs other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + + return EventObject.SequenceEqual(other.EventObject); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableEnumerableObjectEventArgs)obj); + } + + public override int GetHashCode() + { + return HashCodeHelper.GetHashCode(EventObject); + } + } +} diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index 0f3091c46a..d9d670ea59 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Security.Permissions; namespace Umbraco.Core.Events { /// - /// Event args for that can support cancellation + /// Represents event data for events that support cancellation. /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] public class CancellableEventArgs : EventArgs, IEquatable { private bool _cancel; - private Dictionary _eventState; + private IDictionary _eventState; private static readonly ReadOnlyDictionary EmptyAdditionalData = new ReadOnlyDictionary(new Dictionary()); @@ -89,7 +87,7 @@ namespace Umbraco.Core.Events /// /// Returns the EventMessages object which is used to add messages to the message collection for this event /// - public EventMessages Messages { get; private set; } + public EventMessages Messages { get; } /// /// In some cases raised evens might need to contain additional arbitrary readonly data which can be read by event subscribers @@ -98,7 +96,7 @@ namespace Umbraco.Core.Events /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility /// so we cannot change the strongly typed nature for some events. /// - public ReadOnlyDictionary AdditionalData { get; private set; } + public ReadOnlyDictionary AdditionalData { get; internal set; } /// /// This can be used by event subscribers to store state in the event args so they easily deal with custom state data between a starting ("ing") @@ -106,7 +104,8 @@ namespace Umbraco.Core.Events /// public IDictionary EventState { - get { return _eventState ?? (_eventState = new Dictionary()); } + get => _eventState ?? (_eventState = new Dictionary()); + internal set => _eventState = value; } public bool Equals(CancellableEventArgs other) diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index a64a399249..daf36fd6c8 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Security.Permissions; -using Umbraco.Core.Models; +using System.Collections.Generic; namespace Umbraco.Core.Events { /// - /// Used as a base class for the generic type CancellableObjectEventArgs{T} so that we can get direct 'object' access to the underlying EventObject + /// Provides a base class for classes representing event data, for events that support cancellation, and expose an impacted object. /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] public abstract class CancellableObjectEventArgs : CancellableEventArgs { protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) @@ -42,135 +36,11 @@ namespace Umbraco.Core.Events } /// - /// Returns the object relating to the event + /// Gets or sets the impacted object. /// /// /// This is protected so that inheritors can expose it with their own name /// internal object EventObject { get; set; } - - } - - /// - /// Event args for a strongly typed object that can support cancellation - /// - /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable> - { - public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { - } - - public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - } - - public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - } - - public CancellableObjectEventArgs(T eventObject, bool canCancel) - : base(eventObject, canCancel) - { - } - - public CancellableObjectEventArgs(T eventObject) - : base(eventObject) - { - } - - /// - /// Returns the object relating to the event - /// - /// - /// This is protected so that inheritors can expose it with their own name - /// - protected new T EventObject - { - get { return (T) base.EventObject; } - set { base.EventObject = value; } - } - - public bool Equals(CancellableObjectEventArgs other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); - } - - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CancellableObjectEventArgs)obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); - } - } - - public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return !Equals(left, right); - } - } - - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable> - { - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel) - : base(eventObject, canCancel) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject) - : base(eventObject) - { } - - public bool Equals(CancellableEnumerableObjectEventArgs other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - - return EventObject.SequenceEqual(other.EventObject); - } - - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CancellableEnumerableObjectEventArgs)obj); - } - - public override int GetHashCode() - { - return HashCodeHelper.GetHashCode(EventObject); - } } } diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs new file mode 100644 index 0000000000..ace2ce0a4f --- /dev/null +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + /// + /// Represent event data, for events that support cancellation, and expose an impacted object. + /// + /// The type of the exposed, impacted object. + public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable> + { + public CancellableObjectEventArgs(TEventObject eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { + } + + public CancellableObjectEventArgs(TEventObject eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } + + public CancellableObjectEventArgs(TEventObject eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + public CancellableObjectEventArgs(TEventObject eventObject, bool canCancel) + : base(eventObject, canCancel) + { + } + + public CancellableObjectEventArgs(TEventObject eventObject) + : base(eventObject) + { + } + + /// + /// Gets or sets the impacted object. + /// + /// + /// This is protected so that inheritors can expose it with their own name + /// + protected new TEventObject EventObject + { + get => (TEventObject) base.EventObject; + set => base.EventObject = value; + } + + public bool Equals(CancellableObjectEventArgs other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableObjectEventArgs)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); + } + } + + public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs b/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs new file mode 100644 index 0000000000..8c0690d591 --- /dev/null +++ b/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Events +{ + /// + /// Represents event data for the Published event. + /// + public class ContentPublishedEventArgs : PublishEventArgs + { + /// + /// Initializes a new instance of the class. + /// + public ContentPublishedEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { } + + /// + /// Determines whether a culture has been published, during a Published event. + /// + public bool HasPublishedCulture(IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); + + /// + /// Determines whether a culture has been unpublished, during a Published event. + /// + public bool HasUnpublishedCulture(IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); + } +} diff --git a/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs b/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs new file mode 100644 index 0000000000..b64bb19def --- /dev/null +++ b/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Events +{ + /// + /// Represents event data for the Publishing event. + /// + public class ContentPublishingEventArgs : PublishEventArgs + { + /// + /// Initializes a new instance of the class. + /// + public ContentPublishingEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { } + + /// + /// Determines whether a culture is being published, during a Publishing event. + /// + public bool IsPublishingCulture(IContent content, string culture) + => content.PublishCultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty(); + + /// + /// Determines whether a culture is being unpublished, during a Publishing event. + /// + public bool IsUnpublishingCulture(IContent content, string culture) + => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); //bit of a hack since we know that the content implementation tracks changes this way + } +} diff --git a/src/Umbraco.Core/Events/ContentSavedEventArgs.cs b/src/Umbraco.Core/Events/ContentSavedEventArgs.cs new file mode 100644 index 0000000000..e2d8c25dd1 --- /dev/null +++ b/src/Umbraco.Core/Events/ContentSavedEventArgs.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Events +{ + /// + /// Represents event data for the Saved event. + /// + public class ContentSavedEventArgs : SaveEventArgs + { + /// + /// Initializes a new instance of the class. + /// + public ContentSavedEventArgs(IEnumerable eventObject, EventMessages messages, IDictionary additionalData) + : base(eventObject, false, messages, additionalData) + { } + + /// + /// Determines whether a culture has been saved, during a Saved event. + /// + public bool HasSavedCulture(IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UpdatedCulture + culture); + } +} diff --git a/src/Umbraco.Core/Events/ContentSavingEventArgs.cs b/src/Umbraco.Core/Events/ContentSavingEventArgs.cs new file mode 100644 index 0000000000..384353ee2e --- /dev/null +++ b/src/Umbraco.Core/Events/ContentSavingEventArgs.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Events +{ + /// + /// Represent event data for the Saving event. + /// + public class ContentSavingEventArgs : SaveEventArgs + { + #region Factory Methods + + /// + /// Converts to while preserving all args state + /// + /// + public ContentSavedEventArgs ToContentSavedEventArgs() + { + return new ContentSavedEventArgs(EventObject, Messages, AdditionalData) + { + EventState = EventState + }; + } + + /// + /// Converts to while preserving all args state + /// + /// + public ContentPublishedEventArgs ToContentPublishedEventArgs() + { + return new ContentPublishedEventArgs(EventObject, false, Messages) + { + EventState = EventState, + AdditionalData = AdditionalData + }; + } + + /// + /// Converts to while preserving all args state + /// + /// + public ContentPublishingEventArgs ToContentPublishingEventArgs() + { + return new ContentPublishingEventArgs(EventObject, Messages) + { + EventState = EventState, + AdditionalData = AdditionalData + }; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public ContentSavingEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { } + + /// + /// Initializes a new instance of the class. + /// + public ContentSavingEventArgs(IContent eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { } + + #endregion + + /// + /// Determines whether a culture is being saved, during a Saving event. + /// + public bool IsSavingCulture(IContent content, string culture) + => content.CultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty(); + } +} diff --git a/src/Umbraco.Core/Events/EventNameExtractor.cs b/src/Umbraco.Core/Events/EventNameExtractor.cs index 5cb9ca64ef..627426f2ee 100644 --- a/src/Umbraco.Core/Events/EventNameExtractor.cs +++ b/src/Umbraco.Core/Events/EventNameExtractor.cs @@ -35,6 +35,24 @@ namespace Umbraco.Core.Events /// null if not found or an ambiguous match /// public static Attempt FindEvent(Type senderType, Type argsType, Func exclude) + { + var events = FindEvents(senderType, argsType, exclude); + + switch (events.Length) + { + case 0: + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound)); + + case 1: + return Attempt.Succeed(new EventNameExtractorResult(events[0])); + + default: + //there's more than one left so it's ambiguous! + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); + } + } + + internal static string[] FindEvents(Type senderType, Type argsType, Func exclude) { var found = MatchedEventNames.GetOrAdd(new Tuple(senderType, argsType), tuple => { @@ -78,16 +96,7 @@ namespace Umbraco.Core.Events }).Select(x => x.EventInfo.Name).ToArray(); }); - var filtered = found.Where(x => exclude(x) == false).ToArray(); - - if (filtered.Length == 0) - return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound)); - - if (filtered.Length == 1) - return Attempt.Succeed(new EventNameExtractorResult(filtered[0])); - - //there's more than one left so it's ambiguous! - return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); + return found.Where(x => exclude(x) == false).ToArray(); } /// diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 599bae1639..4be655873e 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -10,12 +10,10 @@ namespace Umbraco.Core.Events /// /// /// - /// /// - public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + public PublishEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) { - IsAllRepublished = isAllPublished; } /// @@ -43,12 +41,10 @@ namespace Umbraco.Core.Events /// /// /// - /// /// - public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + public PublishEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) : base(new List { eventObject }, canCancel, eventMessages) { - IsAllRepublished = isAllPublished; } /// @@ -60,7 +56,6 @@ namespace Umbraco.Core.Events public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished) : base(eventObject, canCancel) { - IsAllRepublished = isAllPublished; } /// @@ -90,24 +85,18 @@ namespace Umbraco.Core.Events public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished) : base(new List { eventObject }, canCancel) { - IsAllRepublished = isAllPublished; } /// /// Returns all entities that were published during the operation /// - public IEnumerable PublishedEntities - { - get { return EventObject; } - } - - public bool IsAllRepublished { get; private set; } + public IEnumerable PublishedEntities => EventObject; public bool Equals(PublishEventArgs other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && IsAllRepublished == other.IsAllRepublished; + return base.Equals(other); } public override bool Equals(object obj) @@ -122,7 +111,7 @@ namespace Umbraco.Core.Events { unchecked { - return (base.GetHashCode() * 397) ^ IsAllRepublished.GetHashCode(); + return (base.GetHashCode() * 397); } } diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index 8c3fe82e2f..0b061c2227 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace Umbraco.Core.Events { @@ -113,9 +112,6 @@ namespace Umbraco.Core.Events /// /// Returns all entities that were saved during the operation /// - public IEnumerable SavedEntities - { - get { return EventObject; } - } + public IEnumerable SavedEntities => EventObject; } } diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs index 10a648fd76..c3262d26c6 100644 --- a/src/Umbraco.Core/Exceptions/BootFailedException.cs +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Exceptions /// /// Defines the default boot failed exception message. /// - public const string DefaultMessage = "Boot failed: Umbraco cannot run. Sad. See Umbraco's log file for more details."; + public const string DefaultMessage = "Boot failed: Umbraco cannot run. See Umbraco's log file for more details."; /// /// Initializes a new instance of the class with a specified error message. diff --git a/src/Umbraco.Core/Composing/FactoryExtensions.cs b/src/Umbraco.Core/FactoryExtensions.cs similarity index 98% rename from src/Umbraco.Core/Composing/FactoryExtensions.cs rename to src/Umbraco.Core/FactoryExtensions.cs index 8027f2c7a1..8514525417 100644 --- a/src/Umbraco.Core/Composing/FactoryExtensions.cs +++ b/src/Umbraco.Core/FactoryExtensions.cs @@ -1,8 +1,9 @@ using System; using System.Linq; using System.Reflection; +using Umbraco.Core.Composing; -namespace Umbraco.Core.Composing +namespace Umbraco.Core { /// /// Provides extension methods to the class. diff --git a/src/Umbraco.Core/GuidUtils.cs b/src/Umbraco.Core/GuidUtils.cs index 3768e1385d..d878cee16b 100644 --- a/src/Umbraco.Core/GuidUtils.cs +++ b/src/Umbraco.Core/GuidUtils.cs @@ -40,5 +40,72 @@ namespace Umbraco.Core public DecomposedGuid(Guid value) : this() => this.Value = value; } + + private static readonly char[] Base32Table = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5' + }; + + /// + /// Converts a Guid into a base-32 string. + /// + /// A Guid. + /// The string length. + /// A base-32 encoded string. + /// + /// A base-32 string representation of a Guid is the shortest, efficient, representation + /// that is case insensitive (base-64 is case sensitive). + /// Length must be 1-26, anything else becomes 26. + /// + public static string ToBase32String(Guid guid, int length = 26) + { + if (length <= 0 || length > 26) + length = 26; + + var bytes = guid.ToByteArray(); // a Guid is 128 bits ie 16 bytes + + // this could be optimized by making it unsafe, + // and fixing the table + bytes + chars (see Convert.ToBase64CharArray) + + // each block of 5 bytes = 5*8 = 40 bits + // becomes 40 bits = 8*5 = 8 byte-32 chars + // a Guid is 3 blocks + 8 bits + + // so it turns into a 3*8+2 = 26 chars string + var chars = new char[length]; + + var i = 0; + var j = 0; + + while (i < 15) + { + if (j == length) break; + chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3]; + if (j == length) break; + chars[j++] = Base32Table[((bytes[i] & 0b0000_0111) << 2) | ((bytes[i + 1] & 0b1100_0000) >> 6)]; + if (j == length) break; + chars[j++] = Base32Table[(bytes[i + 1] & 0b0011_1110) >> 1]; + if (j == length) break; + chars[j++] = Base32Table[(bytes[i + 1] & 0b0000_0001) | ((bytes[i + 2] & 0b1111_0000) >> 4)]; + if (j == length) break; + chars[j++] = Base32Table[((bytes[i + 2] & 0b0000_1111) << 1) | ((bytes[i + 3] & 0b1000_0000) >> 7)]; + if (j == length) break; + chars[j++] = Base32Table[(bytes[i + 3] & 0b0111_1100) >> 2]; + if (j == length) break; + chars[j++] = Base32Table[((bytes[i + 3] & 0b0000_0011) << 3) | ((bytes[i + 4] & 0b1110_0000) >> 5)]; + if (j == length) break; + chars[j++] = Base32Table[bytes[i + 4] & 0b0001_1111]; + + i += 5; + } + + if (j < length) + chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3]; + if (j < length) + chars[j] = Base32Table[(bytes[i] & 0b0000_0111) << 2]; + + return new string(chars); + } } } diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 19d29e14ba..8906752dd1 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.IO // shadow support private readonly List _shadowWrappers = new List(); private readonly object _shadowLocker = new object(); - private static Guid _shadowCurrentId = Guid.Empty; // static - unique!! + private static string _shadowCurrentId; // static - unique!! #region Constructor // DI wants a public ctor @@ -45,13 +45,13 @@ namespace Umbraco.Core.IO _shadowWrappers.Clear(); _filesystems.Clear(); Volatile.Write(ref _wkfsInitialized, false); - _shadowCurrentId = Guid.Empty; + _shadowCurrentId = null; } // for tests only, totally unsafe internal static void ResetShadowId() { - _shadowCurrentId = Guid.Empty; + _shadowCurrentId = null; } // set by the scope provider when taking control of filesystems @@ -126,11 +126,11 @@ namespace Umbraco.Core.IO var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts); var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); - _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", IsScoped); - _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", IsScoped); + _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "macro-partials", IsScoped); + _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "partials", IsScoped); _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", IsScoped); _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", IsScoped); - _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", IsScoped); + _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "views", IsScoped); // TODO: do we need a lock here? _shadowWrappers.Add(_macroPartialFileSystem); @@ -146,6 +146,11 @@ namespace Umbraco.Core.IO #region Providers + private readonly Dictionary _paths = new Dictionary(); + + // internal for tests + internal IReadOnlyDictionary Paths => _paths; + /// /// Gets a strongly-typed filesystem. /// @@ -162,10 +167,33 @@ namespace Umbraco.Core.IO return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy(() => { - var name = typeof(TFileSystem).FullName; - if (name == null) throw new Exception("panic!"); + var typeofTFileSystem = typeof(TFileSystem); - var shadowWrapper = CreateShadowWrapper(supporting, "typed/" + name); + // path must be unique and not collide with paths used in CreateWellKnownFileSystems + // for our well-known 'media' filesystem we can use the short 'media' path + // for others, put them under 'x/' and use ... something + string path; + if (typeofTFileSystem == typeof(MediaFileSystem)) + { + path = "media"; + } + else + { + lock (_paths) + { + if (!_paths.TryGetValue(typeofTFileSystem, out path)) + { + path = Guid.NewGuid().ToString("N").Substring(0, 6); + while (_paths.ContainsValue(path)) // this can't loop forever, right? + path = Guid.NewGuid().ToString("N").Substring(0, 6); + _paths[typeofTFileSystem] = path; + } + } + + path = "x/" + path; + } + + var shadowWrapper = CreateShadowWrapper(supporting, path); return _container.CreateInstance(shadowWrapper); })).Value; } @@ -179,35 +207,37 @@ namespace Umbraco.Core.IO // global shadow for the entire application, so great care should be taken to ensure that the // application is *not* doing anything else when using a shadow. - internal ICompletable Shadow(Guid id) + internal ICompletable Shadow() { if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + var id = ShadowWrapper.CreateShadowId(); return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow } - internal void BeginShadow(Guid id) + internal void BeginShadow(string id) { lock (_shadowLocker) { // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId != Guid.Empty) + if (_shadowCurrentId != null) throw new InvalidOperationException("Already shadowing."); + _shadowCurrentId = id; - _logger.Debug("Shadow '{ShadowId}'", id); + _logger.Debug("Shadow '{ShadowId}'", _shadowCurrentId); foreach (var wrapper in _shadowWrappers) - wrapper.Shadow(id); + wrapper.Shadow(_shadowCurrentId); } } - internal void EndShadow(Guid id, bool completed) + internal void EndShadow(string id, bool completed) { lock (_shadowLocker) { // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId == Guid.Empty) + if (_shadowCurrentId == null) throw new InvalidOperationException("Not shadowing."); if (id != _shadowCurrentId) throw new InvalidOperationException("Not the current shadow."); @@ -228,7 +258,7 @@ namespace Umbraco.Core.IO } } - _shadowCurrentId = Guid.Empty; + _shadowCurrentId = null; if (exceptions.Count > 0) throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); @@ -240,7 +270,7 @@ namespace Umbraco.Core.IO lock (_shadowLocker) { var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped); - if (_shadowCurrentId != Guid.Empty) + if (_shadowCurrentId != null) wrapper.Shadow(_shadowCurrentId); _shadowWrappers.Add(wrapper); return wrapper; diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs index 59f8213414..49fe3dc05e 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.IO.MediaPathSchemes /// Implements a combined-guids media path scheme. /// /// - /// Path is "{combinedGuid}/{filename>}" where combinedGuid is a combination of itemGuid and propertyGuid. + /// Path is "{combinedGuid}/{filename}" where combinedGuid is a combination of itemGuid and propertyGuid. /// public class CombinedGuidsMediaPathScheme : IMediaPathScheme { @@ -16,7 +16,9 @@ namespace Umbraco.Core.IO.MediaPathSchemes { // assumes that cuid and puid keys can be trusted - and that a single property type // for a single content cannot store two different files with the same name - var directory = HexEncoder.Encode(GuidUtils.Combine(itemGuid, propertyGuid).ToByteArray()/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/... + + var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid); + var directory = HexEncoder.Encode(combinedGuid.ToByteArray()/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/... return Path.Combine(directory, filename).Replace('\\', '/'); } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs new file mode 100644 index 0000000000..b8f1356041 --- /dev/null +++ b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; + +namespace Umbraco.Core.IO.MediaPathSchemes +{ + /// + /// Implements a unique directory media path scheme. + /// + /// + /// This scheme provides deterministic short paths, with potential collisions. + /// + public class UniqueMediaPathScheme : IMediaPathScheme + { + private const int DirectoryLength = 8; + + /// + public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null) + { + var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid); + var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); + + return Path.Combine(directory, filename).Replace('\\', '/'); + } + + /// + /// + /// Returning null so that does *not* + /// delete any directory. This is because the above shortening of the Guid to 8 chars + /// means we're increasing the risk of collision, and we don't want to delete files + /// belonging to other media items. + /// And, at the moment, we cannot delete directory "only if it is empty" because of + /// race conditions. We'd need to implement locks in for + /// this. + /// + public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => null; + } +} diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 66ef095b04..e4edb2b86b 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -304,7 +304,13 @@ namespace Umbraco.Core.IO // permissions to reach that path, but it may nevertheless be outside of // our root path, due to relative segments, so better check if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + { + // this says that 4.7.2 supports long paths - but Windows does not + // https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception?view=netframework-4.7.2 + if (path.Length > 260) + throw new PathTooLongException($"Path {path} is too long."); return path; + } // nothing prevents us to reach the file, security-wise, yet it is outside // this filesystem's root - throw diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index bce0cc6df7..daec6e8dc5 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.IO private bool _completed; // invoked by the filesystems when shadowing - public ShadowFileSystems(FileSystems fileSystems, Guid id) + public ShadowFileSystems(FileSystems fileSystems, string id) { _fileSystems = fileSystems; Id = id; @@ -19,7 +19,7 @@ namespace Umbraco.Core.IO } // for tests - public Guid Id { get; } + public string Id { get; } // invoked by the scope when exiting, if completed public void Complete() diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index d71f328713..dacef52a92 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -22,7 +22,33 @@ namespace Umbraco.Core.IO _isScoped = isScoped; } - internal void Shadow(Guid id) + public static string CreateShadowId() + { + const int retries = 50; // avoid infinite loop + const int idLength = 8; // 6 chars + + // shorten a Guid to idLength chars, and see whether it collides + // with an existing directory or not - if it does, try again, and + // we should end up with a unique identifier eventually - but just + // detect infinite loops (just in case) + + for (var i = 0; i < retries; i++) + { + var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength); + + var virt = ShadowFsPath + "/" + id; + var shadowDir = IOHelper.MapPath(virt); + if (Directory.Exists(shadowDir)) + continue; + + Directory.CreateDirectory(shadowDir); + return id; + } + + throw new Exception($"Could not get a shadow identifier (tried {retries} times)"); + } + + internal void Shadow(string id) { // note: no thread-safety here, because ShadowFs is thread-safe due to the check // on ShadowFileSystemsScope.None - and if None is false then we should be running diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index 0e5ae8388b..12e3f57d99 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using System.Web; +using System.IO; using Umbraco.Core.Configuration; namespace Umbraco.Core.IO @@ -8,26 +6,11 @@ namespace Umbraco.Core.IO public class SystemFiles { public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; - + + // TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache public static string GetContentCacheXml(IGlobalSettings globalSettings) { - switch (globalSettings.LocalTempStorageLocation) - { - case LocalTempStorage.AspNetTemp: - return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config"); - case LocalTempStorage.EnvironmentTemp: - var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); - var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", - //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back - // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not - // utilizing an old path - appDomainHash); - return Path.Combine(cachePath, "umbraco.config"); - case LocalTempStorage.Default: - return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config"); - default: - throw new ArgumentOutOfRangeException(); - } + return Path.Combine(globalSettings.LocalTempPath, "umbraco.config"); } } } diff --git a/src/Umbraco.Core/Logging/LogProfiler.cs b/src/Umbraco.Core/Logging/LogProfiler.cs index b80e40942a..74dae545b4 100644 --- a/src/Umbraco.Core/Logging/LogProfiler.cs +++ b/src/Umbraco.Core/Logging/LogProfiler.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Logging } // a lightweight disposable timer - private class LightDisposableTimer : DisposableObject + private class LightDisposableTimer : DisposableObjectSlim { private readonly Action _callback; private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); diff --git a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs index 6775ea7417..200b2fadea 100644 --- a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs +++ b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.Logging.Serilog //Set this environment variable - so that it can be used in external config file //add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" /> Environment.SetEnvironmentVariable("BASEDIR", AppDomain.CurrentDomain.BaseDirectory, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process); logConfig.MinimumLevel.Verbose() //Set to highest level of logging (as any sinks may want to restrict it to Errors only) .Enrich.WithProcessId() diff --git a/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs b/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs index 17d86b45e1..bfb0c6dde3 100644 --- a/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs +++ b/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs @@ -41,8 +41,6 @@ namespace Umbraco.Core.Logging.Serilog var loggerConfig = new LoggerConfiguration(); loggerConfig .MinimalConfiguration() - .OutputDefaultTextFile(LogEventLevel.Debug) - .OutputDefaultJsonFile() .ReadFromConfigFile() .ReadFromUserConfigFile(); diff --git a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs index ea2c8aa49f..4c0e8ed4f8 100644 --- a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Newtonsoft.Json; using Serilog.Events; using Serilog.Formatting.Compact.Reader; @@ -10,13 +11,15 @@ namespace Umbraco.Core.Logging.Viewer internal class JsonLogViewer : LogViewerSourceBase { private readonly string _logsPath; + private readonly ILogger _logger; - public JsonLogViewer(string logsPath = "", string searchPath = "") : base(searchPath) + public JsonLogViewer(ILogger logger, string logsPath = "", string searchPath = "") : base(searchPath) { if (string.IsNullOrEmpty(logsPath)) logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\"; _logsPath = logsPath; + _logger = logger; } private const int FileSizeCap = 100; @@ -77,8 +80,14 @@ namespace Umbraco.Core.Logging.Viewer using (var stream = new StreamReader(fs)) { var reader = new LogEventReader(stream); - while (reader.TryRead(out var evt)) + while (TryRead(reader, out var evt)) { + //We may get a null if log line is malformed + if (evt == null) + { + continue; + } + if (count > skip + take) { break; @@ -105,5 +114,21 @@ namespace Umbraco.Core.Logging.Viewer return logs; } + private bool TryRead(LogEventReader reader, out LogEvent evt) + { + try + { + return reader.TryRead(out evt); + } + catch (JsonReaderException ex) + { + // As we are reading/streaming one line at a time in the JSON file + // Thus we can not report the line number, as it will always be 1 + _logger.Error(ex, "Unable to parse a line in the JSON log file"); + + evt = null; + return true; + } + } } } diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs index 5ddcb01078..8eb835b4d9 100644 --- a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs +++ b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Components; +using Umbraco.Core.Compose; +using Umbraco.Core.Composing; namespace Umbraco.Core.Logging.Viewer { @@ -8,7 +9,7 @@ namespace Umbraco.Core.Logging.Viewer { public void Compose(Composition composition) { - composition.SetLogViewer(_ => new JsonLogViewer()); + composition.SetLogViewer(_ => new JsonLogViewer(composition.Logger)); } } } diff --git a/src/Umbraco.Core/Logging/VoidProfiler.cs b/src/Umbraco.Core/Logging/VoidProfiler.cs index a33f1b2444..24eb8e81c3 100644 --- a/src/Umbraco.Core/Logging/VoidProfiler.cs +++ b/src/Umbraco.Core/Logging/VoidProfiler.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Logging public void Stop(bool discardResults = false) { } - private class VoidDisposable : DisposableObject + private class VoidDisposable : DisposableObjectSlim { protected override void DisposeResources() { } diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 6ca3b916ea..dc40cd90a2 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -101,7 +101,7 @@ namespace Umbraco.Core.Manifest var gridEditors = new List(); var contentApps = new List(); var dashboards = new List(); - var sections = new List(); + var sections = new List(); foreach (var manifest in manifests) { diff --git a/src/Umbraco.Core/Manifest/ManifestBackOfficeSection.cs b/src/Umbraco.Core/Manifest/ManifestSection.cs similarity index 75% rename from src/Umbraco.Core/Manifest/ManifestBackOfficeSection.cs rename to src/Umbraco.Core/Manifest/ManifestSection.cs index a1b89d9a01..584e2a157b 100644 --- a/src/Umbraco.Core/Manifest/ManifestBackOfficeSection.cs +++ b/src/Umbraco.Core/Manifest/ManifestSection.cs @@ -1,10 +1,10 @@ using System.Runtime.Serialization; -using Umbraco.Core.Models.Trees; +using Umbraco.Core.Models.Sections; namespace Umbraco.Core.Manifest { [DataContract(Name = "section", Namespace = "")] - public class ManifestBackOfficeSection : IBackOfficeSection + public class ManifestSection : ISection { [DataMember(Name = "alias")] public string Alias { get; set; } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index c272449509..475ee8a7f8 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -55,6 +55,6 @@ namespace Umbraco.Core.Manifest /// Gets or sets the sections listed in the manifest. /// [JsonProperty("sections")] - public ManifestBackOfficeSection[] Sections { get; set; } = Array.Empty(); + public ManifestSection[] Sections { get; set; } = Array.Empty(); } } diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index 62faa45563..99b162e0b7 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -1,8 +1,24 @@ -namespace Umbraco.Core.Media +using System.Collections.Generic; + +namespace Umbraco.Core.Media { public interface IEmbedProvider { - bool SupportsDimensions { get; } + /// + /// The OEmbed API Endpoint + /// + string ApiEndpoint { get; } + + /// + /// A string array of Regex patterns to match against the pasted OEmbed URL + /// + string[] UrlSchemeRegex { get; } + + /// + /// A collection of querystring request parameters to append to the API Url + /// + /// ?key=value&key2=value2 + Dictionary RequestParams { get; } string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); } diff --git a/src/Umbraco.Core/Media/IEmbedSettingProvider.cs b/src/Umbraco.Core/Media/IEmbedSettingProvider.cs deleted file mode 100644 index b9ba611100..0000000000 --- a/src/Umbraco.Core/Media/IEmbedSettingProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Xml; - -namespace Umbraco.Core.Media -{ - public interface IEmbedSettingProvider - { - object GetSetting(XmlNode settingNode); - } -} diff --git a/src/Umbraco.Core/Media/Result.cs b/src/Umbraco.Core/Media/OEmbedResult.cs similarity index 53% rename from src/Umbraco.Core/Media/Result.cs rename to src/Umbraco.Core/Media/OEmbedResult.cs index a8683d03d5..bed07d9b5e 100644 --- a/src/Umbraco.Core/Media/Result.cs +++ b/src/Umbraco.Core/Media/OEmbedResult.cs @@ -1,10 +1,8 @@ namespace Umbraco.Core.Media { - - // TODO: Could definitely have done with a better name - public class Result + public class OEmbedResult { - public Status Status { get; set; } + public OEmbedStatus OEmbedStatus { get; set; } public bool SupportsDimensions { get; set; } public string Markup { get; set; } } diff --git a/src/Umbraco.Core/Media/Status.cs b/src/Umbraco.Core/Media/OEmbedStatus.cs similarity index 55% rename from src/Umbraco.Core/Media/Status.cs rename to src/Umbraco.Core/Media/OEmbedStatus.cs index abbcca97da..0f1f22b0e2 100644 --- a/src/Umbraco.Core/Media/Status.cs +++ b/src/Umbraco.Core/Media/OEmbedStatus.cs @@ -1,8 +1,6 @@ namespace Umbraco.Core.Media { - - //NOTE: Could definitely have done with a better name - public enum Status + public enum OEmbedStatus { NotSupported, Error, diff --git a/src/Umbraco.Core/Media/ProviderSetting.cs b/src/Umbraco.Core/Media/ProviderSetting.cs deleted file mode 100644 index 9fef5efabf..0000000000 --- a/src/Umbraco.Core/Media/ProviderSetting.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Umbraco.Core.Media -{ - public class ProviderSetting : Attribute - { - } -} diff --git a/src/Umbraco.Core/Migrations/IMigrationContext.cs b/src/Umbraco.Core/Migrations/IMigrationContext.cs index 80ba78b6de..2baadc79eb 100644 --- a/src/Umbraco.Core/Migrations/IMigrationContext.cs +++ b/src/Umbraco.Core/Migrations/IMigrationContext.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Logging; +using System; +using System.Collections.Generic; +using Umbraco.Core.Logging; using Umbraco.Core.Persistence; namespace Umbraco.Core.Migrations @@ -32,5 +34,11 @@ namespace Umbraco.Core.Migrations /// Gets or sets a value indicating whether an expression is being built. /// bool BuildingExpression { get; set; } + + /// + /// Adds a post-migrations. + /// + void AddPostMigration() + where TMigration : IMigration; } } diff --git a/src/Umbraco.Core/Migrations/IPostMigration.cs b/src/Umbraco.Core/Migrations/IPostMigration.cs deleted file mode 100644 index 15daf0fc74..0000000000 --- a/src/Umbraco.Core/Migrations/IPostMigration.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Semver; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Migrations -{ - public interface IPostMigration : IDiscoverable - { - void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger); - } -} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index 9c6ff208f8..5c4defab0c 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -27,7 +27,6 @@ namespace Umbraco.Core.Migrations.Install private readonly IRuntimeState _runtime; private readonly IMigrationBuilder _migrationBuilder; private readonly IKeyValueService _keyValueService; - private readonly PostMigrationCollection _postMigrations; private readonly ILogger _logger; private DatabaseSchemaResult _databaseSchemaValidationResult; @@ -35,7 +34,7 @@ namespace Umbraco.Core.Migrations.Install /// /// Initializes a new instance of the class. /// - public DatabaseBuilder(IScopeProvider scopeProvider, IGlobalSettings globalSettings, IUmbracoDatabaseFactory databaseFactory, IRuntimeState runtime, ILogger logger, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations) + public DatabaseBuilder(IScopeProvider scopeProvider, IGlobalSettings globalSettings, IUmbracoDatabaseFactory databaseFactory, IRuntimeState runtime, ILogger logger, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService) { _scopeProvider = scopeProvider; _globalSettings = globalSettings; @@ -44,7 +43,6 @@ namespace Umbraco.Core.Migrations.Install _logger = logger; _migrationBuilder = migrationBuilder; _keyValueService = keyValueService; - _postMigrations = postMigrations; } #region Status @@ -483,7 +481,7 @@ namespace Umbraco.Core.Migrations.Install /// configured and it is possible to connect to the database. /// Runs whichever migrations need to run. /// - public Result UpgradeSchemaAndData() + public Result UpgradeSchemaAndData(MigrationPlan plan) { try { @@ -496,8 +494,8 @@ namespace Umbraco.Core.Migrations.Install _logger.Info("Database upgrade started"); // upgrade - var upgrader = new UmbracoUpgrader(); - upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger, _postMigrations); + var upgrader = new Upgrader(plan); + upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger); var message = "

Upgrade completed!

"; diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 7e9df321c3..f0e6dd2e5b 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Migrations.Install if (tableName.Equals(Constants.DatabaseSchema.Tables.RelationType)) CreateRelationTypeData(); - + if (tableName.Equals(Constants.DatabaseSchema.Tables.KeyValue)) CreateKeyValueData(); @@ -114,10 +114,10 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); @@ -210,14 +210,14 @@ namespace Umbraco.Core.Migrations.Install private void CreatePropertyTypeData() { - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); //membership property types _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); @@ -271,17 +271,17 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -88, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -89, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -90, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); - InsertDataTypeDto(Constants.DataTypes.LabelString, Constants.PropertyEditors.Aliases.NoEdit, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelInt, Constants.PropertyEditors.Aliases.NoEdit, "Integer", "{\"umbracoDataValueType\":\"INT\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelBigint, Constants.PropertyEditors.Aliases.NoEdit, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelDateTime, Constants.PropertyEditors.Aliases.NoEdit, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelDecimal, Constants.PropertyEditors.Aliases.NoEdit, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelTime, Constants.PropertyEditors.Aliases.NoEdit, "Date", "{\"umbracoDataValueType\":\"TIME\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelString, Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelInt, Constants.PropertyEditors.Aliases.Label, "Integer", "{\"umbracoDataValueType\":\"INT\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelBigint, Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelDateTime, Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelDecimal, Constants.PropertyEditors.Aliases.Label, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelTime, Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"TIME\"}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -36, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" }); InsertDataTypeDto(Constants.DataTypes.DropDownSingle, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = Constants.PropertyEditors.Aliases.Date, DbType = "Date" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.DateTime", DbType = "Date", Configuration = "{\"format\":\"YYYY-MM-DD\"}" }); InsertDataTypeDto(Constants.DataTypes.DropDownMultiple, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", @@ -321,7 +321,7 @@ namespace Umbraco.Core.Migrations.Install { // on install, initialize the umbraco migration plan with the final state - var upgrader = new UmbracoUpgrader(); + var upgrader = new Upgrader(new UmbracoPlan()); var stateValueKey = upgrader.StateValueKey; var finalState = upgrader.Plan.FinalState; diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index 5cc818a6d2..d8283fd112 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -50,8 +50,6 @@ namespace Umbraco.Core.Migrations.Install typeof (MemberTypeDto), typeof (MemberDto), typeof (Member2MemberGroupDto), - typeof (ContentXmlDto), - typeof (PreviewXmlDto), typeof (PropertyTypeGroupDto), typeof (PropertyTypeDto), typeof (PropertyDataDto), @@ -139,13 +137,18 @@ namespace Umbraco.Core.Migrations.Install /// Validates the schema of the current database. ///
internal DatabaseSchemaResult ValidateSchema() + { + return ValidateSchema(OrderedTables); + } + + internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables) { var result = new DatabaseSchemaResult(SqlSyntax); result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database) .Select(x => new DbIndexDefinition(x))); - result.TableDefinitions.AddRange(OrderedTables + result.TableDefinitions.AddRange(orderedTables .Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax))); ValidateDbTables(result); diff --git a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs index 6d936e8407..bf07e4d08f 100644 --- a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs +++ b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs @@ -96,6 +96,12 @@ namespace Umbraco.Core.Migrations return tables.Any(x => x.InvariantEquals(tableName)); } + protected bool IndexExists(string indexName) + { + var indexes = SqlSyntax.GetDefinedIndexes(Context.Database); + return indexes.Any(x => x.Item2.InvariantEquals(indexName)); + } + protected bool ColumnExists(string tableName, string columnName) { var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); diff --git a/src/Umbraco.Core/Migrations/MigrationContext.cs b/src/Umbraco.Core/Migrations/MigrationContext.cs index da454fab03..a8d052036c 100644 --- a/src/Umbraco.Core/Migrations/MigrationContext.cs +++ b/src/Umbraco.Core/Migrations/MigrationContext.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; namespace Umbraco.Core.Migrations { /// - /// Represents a migration context. + /// Implements . /// internal class MigrationContext : IMigrationContext { @@ -32,5 +33,15 @@ namespace Umbraco.Core.Migrations /// public bool BuildingExpression { get; set; } + + // this is only internally exposed + public List PostMigrations { get; } = new List(); + + /// + public void AddPostMigration() + where TMigration : IMigration + { + PostMigrations.Add(typeof(TMigration)); + } } } diff --git a/src/Umbraco.Core/Migrations/MigrationPlan.cs b/src/Umbraco.Core/Migrations/MigrationPlan.cs index 9ede9f04f2..4be4ae7d30 100644 --- a/src/Umbraco.Core/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Core/Migrations/MigrationPlan.cs @@ -14,6 +14,7 @@ namespace Umbraco.Core.Migrations public class MigrationPlan { private readonly Dictionary _transitions = new Dictionary(); + private readonly List _postMigrationTypes = new List(); private string _prevState; private string _finalState; @@ -166,6 +167,25 @@ namespace Umbraco.Core.Migrations return this; } + /// + /// Prepares post-migrations. + /// + /// + /// This can be overriden to filter, complement, and/or re-order post-migrations. + /// + protected virtual IEnumerable PreparePostMigrations(IEnumerable types) + => types; + + /// + /// Adds a post-migration to the plan. + /// + public virtual MigrationPlan AddPostMigration() + where TMigration : IMigration + { + _postMigrationTypes.Add(typeof(TMigration)); + return this; + } + /// /// Creates a random, unique state. /// @@ -270,6 +290,7 @@ namespace Umbraco.Core.Migrations throw new Exception($"Unknown state \"{origState}\"."); var context = new MigrationContext(scope.Database, logger); + context.PostMigrations.AddRange(_postMigrationTypes); while (transition != null) { @@ -285,6 +306,20 @@ namespace Umbraco.Core.Migrations throw new Exception($"Unknown state \"{origState}\"."); } + // prepare and de-duplicate post-migrations, only keeping the 1st occurence + var temp = new HashSet(); + var postMigrationTypes = PreparePostMigrations(context.PostMigrations) + .Where(x => !temp.Contains(x)) + .Select(x => { temp.Add(x); return x; }); + + // run post-migrations + foreach (var postMigrationType in postMigrationTypes) + { + logger.Info($"PostMigration: {postMigrationType.FullName}."); + var postMigration = migrationBuilder.Build(postMigrationType, context); + postMigration.Migrate(); + } + logger.Info("Done (pending scope completion)."); // safety check diff --git a/src/Umbraco.Core/Migrations/PostMigrationCollection.cs b/src/Umbraco.Core/Migrations/PostMigrationCollection.cs deleted file mode 100644 index 488bdfd72d..0000000000 --- a/src/Umbraco.Core/Migrations/PostMigrationCollection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Migrations -{ - public class PostMigrationCollection : BuilderCollectionBase - { - public PostMigrationCollection(IEnumerable items) - : base(items) - { } - } -} diff --git a/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs b/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs deleted file mode 100644 index b23d4f1c9c..0000000000 --- a/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Migrations -{ - public class PostMigrationCollectionBuilder : LazyCollectionBuilderBase - { - protected override PostMigrationCollectionBuilder This => this; - - protected override Lifetime CollectionLifetime => Lifetime.Transient; - } -} diff --git a/src/Umbraco.Core/Migrations/PostMigrations/IPublishedSnapshotRebuilder.cs b/src/Umbraco.Core/Migrations/PostMigrations/IPublishedSnapshotRebuilder.cs new file mode 100644 index 0000000000..1b0549827e --- /dev/null +++ b/src/Umbraco.Core/Migrations/PostMigrations/IPublishedSnapshotRebuilder.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Migrations.PostMigrations +{ + /// + /// Rebuilds the published snapshot. + /// + /// + /// This interface exists because the entire published snapshot lives in Umbraco.Web + /// but we may want to trigger rebuilds from Umbraco.Core. These two assemblies should + /// be refactored, really. + /// + public interface IPublishedSnapshotRebuilder + { + /// + /// Rebuilds. + /// + void Rebuild(); + } +} diff --git a/src/Umbraco.Core/Migrations/PostMigrations/PublishedSnapshotRebuilder.cs b/src/Umbraco.Core/Migrations/PostMigrations/PublishedSnapshotRebuilder.cs new file mode 100644 index 0000000000..acc943b297 --- /dev/null +++ b/src/Umbraco.Core/Migrations/PostMigrations/PublishedSnapshotRebuilder.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Migrations.PostMigrations +{ + /// + /// Implements in Umbraco.Core (doing nothing). + /// + public class PublishedSnapshotRebuilder : IPublishedSnapshotRebuilder + { + /// + public void Rebuild() + { } + } +} diff --git a/src/Umbraco.Core/Migrations/PostMigrations/RebuildPublishedSnapshot.cs b/src/Umbraco.Core/Migrations/PostMigrations/RebuildPublishedSnapshot.cs new file mode 100644 index 0000000000..4905699fd4 --- /dev/null +++ b/src/Umbraco.Core/Migrations/PostMigrations/RebuildPublishedSnapshot.cs @@ -0,0 +1,24 @@ +namespace Umbraco.Core.Migrations.PostMigrations +{ + /// + /// Rebuilds the published snapshot. + /// + public class RebuildPublishedSnapshot : IMigration + { + private readonly IPublishedSnapshotRebuilder _rebuilder; + + /// + /// Initializes a new instance of the class. + /// + public RebuildPublishedSnapshot(IPublishedSnapshotRebuilder rebuilder) + { + _rebuilder = rebuilder; + } + + /// + public void Migrate() + { + _rebuilder.Rebuild(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 9bf58c8d2f..fa24cba21f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Migrations.Upgrade.V_7_12_0; using Umbraco.Core.Migrations.Upgrade.V_7_14_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; +using Umbraco.Core.Migrations.Upgrade.V_8_0_1; namespace Umbraco.Core.Migrations.Upgrade { @@ -36,8 +37,8 @@ namespace Umbraco.Core.Migrations.Upgrade get { // no state in database yet - assume we have something in web.config that makes some sense - if (!SemVersion.TryParse(ConfigurationManager.AppSettings["umbracoConfigurationStatus"], out var currentVersion)) - throw new InvalidOperationException("Could not get current version from web.config umbracoConfigurationStatus appSetting."); + if (!SemVersion.TryParse(ConfigurationManager.AppSettings[Constants.AppSettings.ConfigurationStatus], out var currentVersion)) + throw new InvalidOperationException($"Could not get current version from web.config {Constants.AppSettings.ConfigurationStatus} appSetting."); // we currently support upgrading from 7.10.0 and later if (currentVersion < new SemVersion(7, 10)) @@ -56,7 +57,7 @@ namespace Umbraco.Core.Migrations.Upgrade } } - // define the plan + // define the plan protected void DefinePlan() { // MODIFYING THE PLAN @@ -126,7 +127,18 @@ namespace Umbraco.Core.Migrations.Upgrade To("{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); // from 7.14.0 To("{ED28B66A-E248-4D94-8CDB-9BDF574023F0}"); To("{38C809D5-6C34-426B-9BEA-EFD39162595C}"); + To("{6017F044-8E70-4E10-B2A3-336949692ADD}"); + To("{98339BEF-E4B2-48A8-B9D1-D173DC842BBE}"); + Merge() + .To("{CDBEDEE4-9496-4903-9CF2-4104E00FF960}") + .With() + .To("{940FD19A-00A8-4D5C-B8FF-939143585726}") + .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); + + To("{E0CBE54D-A84F-4A8F-9B13-900945FD7ED9}"); + To("{78BAF571-90D0-4D28-8175-EF96316DA789}"); + To("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}"); //FINAL @@ -158,7 +170,7 @@ namespace Umbraco.Core.Migrations.Upgrade From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0 - + // 7.14.0 has migrations, handle it... // clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then // clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations), diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoUpgrader.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoUpgrader.cs deleted file mode 100644 index fa29e80a6b..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoUpgrader.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Configuration; -using Semver; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Migrations.Upgrade -{ - /// - /// Represents the Umbraco upgrader. - /// - public class UmbracoUpgrader : Upgrader - { - private PostMigrationCollection _postMigrations; - - /// - /// Initializes a new instance of the class. - /// - public UmbracoUpgrader() - : base(new UmbracoPlan()) - { } - - /// - /// Executes. - /// - public void Execute(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, ILogger logger, PostMigrationCollection postMigrations) - { - _postMigrations = postMigrations; - Execute(scopeProvider, migrationBuilder, keyValueService, logger); - } - - /// - public override void AfterMigrations(IScope scope, ILogger logger) - { - // assume we have something in web.config that makes some sense = the origin version - if (!SemVersion.TryParse(ConfigurationManager.AppSettings["umbracoConfigurationStatus"], out var originVersion)) - throw new InvalidOperationException("Could not get current version from web.config umbracoConfigurationStatus appSetting."); - - // target version is the code version - var targetVersion = UmbracoVersion.SemanticVersion; - - foreach (var postMigration in _postMigrations) - postMigration.Execute(Name, scope, originVersion, targetVersion, logger); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs index cb8a58f3e3..2f20f01728 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs @@ -53,7 +53,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var dataTypeDto = new DataTypeDto { NodeId = id, - EditorAlias = Constants.PropertyEditors.Aliases.NoEdit, + EditorAlias = Constants.PropertyEditors.Aliases.Label, DbType = dbType }; diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs new file mode 100644 index 0000000000..0ccc2d93ff --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs @@ -0,0 +1,22 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddUserLoginDtoDateIndex : MigrationBase + { + public AddUserLoginDtoDateIndex(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + if (!IndexExists("IX_umbracoUserLogin_lastValidatedUtc")) + Create.Index("IX_umbracoUserLogin_lastValidatedUtc") + .OnTable(UserLoginDto.TableName) + .OnColumn("lastValidatedUtc") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs index 23c835a327..84dd393b0d 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using NPoco; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 @@ -59,5 +61,58 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Database.Update(dto); } } + + // we *need* to use this private DTO here, which does *not* have extra properties, which would kill the migration + + [TableName(TableName)] + [PrimaryKey("pk")] + [ExplicitColumns] + private class ContentTypeDto + { + public const string TableName = Constants.DatabaseSchema.Tables.ContentType; + + [Column("pk")] + [PrimaryKeyColumn(IdentitySeed = 535)] + public int PrimaryKey { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")] + public int NodeId { get; set; } + + [Column("alias")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Alias { get; set; } + + [Column("icon")] + [Index(IndexTypes.NonClustered)] + [NullSetting(NullSetting = NullSettings.Null)] + public string Icon { get; set; } + + [Column("thumbnail")] + [Constraint(Default = "folder.png")] + public string Thumbnail { get; set; } + + [Column("description")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(1500)] + public string Description { get; set; } + + [Column("isContainer")] + [Constraint(Default = "0")] + public bool IsContainer { get; set; } + + [Column("allowAtRoot")] + [Constraint(Default = "0")] + public bool AllowAtRoot { get; set; } + + [Column("variations")] + [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] + public byte Variations { get; set; } + + [ResultColumn] + public NodeDto NodeDto { get; set; } + } + } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs index 008b3e4b5f..061b96976a 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs @@ -1,6 +1,5 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { - public class DropTaskTables : MigrationBase { public DropTaskTables(IMigrationContext context) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropXmlTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropXmlTables.cs new file mode 100644 index 0000000000..be79178932 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropXmlTables.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class DropXmlTables : MigrationBase + { + public DropXmlTables(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + if (TableExists("cmsContentXml")) + Delete.Table("cmsContentXml").Do(); + if (TableExists("cmsPreviewXml")) + Delete.Table("cmsPreviewXml").Do(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs new file mode 100644 index 0000000000..a434b9f1c1 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class MergeDateAndDateTimePropertyEditor : MigrationBase + { + public MergeDateAndDateTimePropertyEditor(IMigrationContext context) + : base(context) + { + } + + public override void Migrate() + { + var dataTypes = GetDataTypes("Umbraco.Date"); + + foreach (var dataType in dataTypes) + { + DateTimeConfiguration config; + try + { + config = (DateTimeConfiguration) new CustomDateTimeConfigurationEditor().FromDatabase( + dataType.Configuration); + } + catch (Exception ex) + { + Logger.Error( + ex, + "Invalid property editor configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", + dataType.Configuration); + + continue; + } + + config.OffsetTime = false; + + dataType.EditorAlias = Constants.PropertyEditors.Aliases.DateTime; + dataType.Configuration = ConfigurationEditor.ToDatabase(config); + + Database.Update(dataType); + } + } + + + + private List GetDataTypes(string editorAlias) + { + //need to convert the old drop down data types to use the new one + var dataTypes = Database.Fetch(Sql() + .Select() + .From() + .Where(x => x.EditorAlias == editorAlias)); + return dataTypes; + } + + + + private class CustomDateTimeConfigurationEditor : ConfigurationEditor + { + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs new file mode 100644 index 0000000000..1944c8079f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.PostMigrations; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RadioAndCheckboxAndDropdownPropertyEditorsMigration : MigrationBase + { + public RadioAndCheckboxAndDropdownPropertyEditorsMigration(IMigrationContext context) + : base(context) + { + } + + public override void Migrate() + { + var refreshCache = false; + + refreshCache |= Migrate(Constants.PropertyEditors.Aliases.RadioButtonList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, true)); + refreshCache |= Migrate(Constants.PropertyEditors.Aliases.CheckBoxList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, false)); + refreshCache |= Migrate(Constants.PropertyEditors.Aliases.DropDownListFlexible, UpdateDropDownPropertyDataDto); + + if (refreshCache) + { + Context.AddPostMigration(); + } + } + + private bool Migrate(string editorAlias, Func updateRadioPropertyDataFunc) + { + var refreshCache = false; + var dataTypes = GetDataTypes(editorAlias); + + foreach (var dataType in dataTypes) + { + ValueListConfiguration config; + + if (dataType.Configuration.IsNullOrWhiteSpace()) + continue; + + // parse configuration, and update everything accordingly + try + { + config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase( + dataType.Configuration); + } + catch (Exception ex) + { + Logger.Error( + ex, + "Invalid property editor configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", + dataType.Configuration); + + continue; + } + + // get property data dtos + var propertyDataDtos = Database.Fetch(Sql() + .Select() + .From() + .InnerJoin() + .On((pt, pd) => pt.Id == pd.PropertyTypeId) + .InnerJoin() + .On((dt, pt) => dt.NodeId == pt.DataTypeId) + .Where(x => x.DataTypeId == dataType.NodeId)); + + // update dtos + var updatedDtos = propertyDataDtos.Where(x => updateRadioPropertyDataFunc(x, config)); + + // persist changes + foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); + + UpdateDataType(dataType); + refreshCache = true; + } + + return refreshCache; + } + + private List GetDataTypes(string editorAlias) + { + //need to convert the old drop down data types to use the new one + var dataTypes = Database.Fetch(Sql() + .Select() + .From() + .Where(x => x.EditorAlias == editorAlias)); + return dataTypes; + } + + private void UpdateDataType(DataTypeDto dataType) + { + dataType.DbType = ValueStorageType.Nvarchar.ToString(); + Database.Update(dataType); + } + + private bool UpdateRadioOrCheckboxPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config, bool singleValue) + { + //Get the INT ids stored for this property/drop down + int[] ids = null; + if (!propData.VarcharValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.VarcharValue); + } + else if (!propData.TextValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.TextValue); + } + else if (propData.IntegerValue.HasValue) + { + ids = new[] {propData.IntegerValue.Value}; + } + + //if there are INT ids, convert them to values based on the configuration + if (ids == null || ids.Length <= 0) return false; + + //map the ids to values + var values = new List(); + var canConvert = true; + + foreach (var id in ids) + { + var val = config.Items.FirstOrDefault(x => x.Id == id); + if (val != null) + values.Add(val.Value); + else + { + Logger.Warn( + "Could not find associated data type configuration for stored Id {DataTypeId}", id); + canConvert = false; + } + } + + if (!canConvert) return false; + + //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first + propData.VarcharValue = singleValue ? values[0] : JsonConvert.SerializeObject(values); + propData.TextValue = null; + propData.IntegerValue = null; + return true; + } + + private bool UpdateDropDownPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) + { + //Get the INT ids stored for this property/drop down + var values = propData.VarcharValue.Split(new []{","}, StringSplitOptions.RemoveEmptyEntries); + + //if there are INT ids, convert them to values based on the configuration + if (values == null || values.Length <= 0) return false; + + //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first + propData.VarcharValue = JsonConvert.SerializeObject(values); + propData.TextValue = null; + propData.IntegerValue = null; + return true; + } + + private int[] ConvertStringValues(string val) + { + var splitVals = val.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + + var intVals = splitVals + .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) + .Where(x => x != int.MinValue) + .ToArray(); + + //only return if the number of values are the same (i.e. All INTs) + if (splitVals.Length == intVals.Length) + return intVals; + + return null; + } + + private class ValueListConfigurationEditor : ConfigurationEditor + { + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameLabelAndRichTextPropertyEditorAliases.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameLabelAndRichTextPropertyEditorAliases.cs new file mode 100644 index 0000000000..1252a26e68 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameLabelAndRichTextPropertyEditorAliases.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RenameLabelAndRichTextPropertyEditorAliases : MigrationBase + { + public RenameLabelAndRichTextPropertyEditorAliases(IMigrationContext context) + : base(context) + { + } + + public override void Migrate() + { + MigratePropertyEditorAlias("Umbraco.TinyMCEv3", Constants.PropertyEditors.Aliases.TinyMce); + MigratePropertyEditorAlias("Umbraco.NoEdit", Constants.PropertyEditors.Aliases.Label); + } + + private void MigratePropertyEditorAlias(string oldAlias, string newAlias) + { + var dataTypes = GetDataTypes(oldAlias); + + foreach (var dataType in dataTypes) + { + dataType.EditorAlias = newAlias; + Database.Update(dataType); + } + } + + private List GetDataTypes(string editorAlias) + { + var dataTypes = Database.Fetch(Sql() + .Select() + .From() + .Where(x => x.EditorAlias == editorAlias)); + return dataTypes; + } + + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs new file mode 100644 index 0000000000..9bbccf368c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RenameUmbracoDomainsTable : MigrationBase + { + public RenameUmbracoDomainsTable(IMigrationContext context) : base(context) { } + + public override void Migrate() + { + Rename.Table("umbracoDomains").To(Constants.DatabaseSchema.Tables.Domain).Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs new file mode 100644 index 0000000000..f6850eb254 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Migrations.PostMigrations; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_1 +{ + public class ChangeNuCacheJsonFormat : MigrationBase + { + public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + // nothing - just adding the post-migration + Context.AddPostMigration(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs new file mode 100644 index 0000000000..0ceb366e1c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Migrations.PostMigrations; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class ChangeNuCacheJsonFormat : MigrationBase + { + public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + // nothing - just adding the post-migration + Context.AddPostMigration(); + } + } +} diff --git a/src/Umbraco.Core/Models/AuditEntry.cs b/src/Umbraco.Core/Models/AuditEntry.cs index 2076e5328c..d12163f394 100644 --- a/src/Umbraco.Core/Models/AuditEntry.cs +++ b/src/Umbraco.Core/Models/AuditEntry.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -12,8 +11,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] internal class AuditEntry : EntityBase, IAuditEntry { - private static PropertySelectors _selectors; - private int _performingUserId; private string _performingDetails; private string _performingIp; @@ -22,38 +19,25 @@ namespace Umbraco.Core.Models private string _eventType; private string _eventDetails; - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - - private class PropertySelectors - { - public readonly PropertyInfo PerformingUserId = ExpressionHelper.GetPropertyInfo(x => x.PerformingUserId); - public readonly PropertyInfo PerformingDetails = ExpressionHelper.GetPropertyInfo(x => x.PerformingDetails); - public readonly PropertyInfo PerformingIp = ExpressionHelper.GetPropertyInfo(x => x.PerformingIp); - public readonly PropertyInfo AffectedUserId = ExpressionHelper.GetPropertyInfo(x => x.AffectedUserId); - public readonly PropertyInfo AffectedDetails = ExpressionHelper.GetPropertyInfo(x => x.AffectedDetails); - public readonly PropertyInfo EventType = ExpressionHelper.GetPropertyInfo(x => x.EventType); - public readonly PropertyInfo EventDetails = ExpressionHelper.GetPropertyInfo(x => x.EventDetails); - } - /// public int PerformingUserId { get => _performingUserId; - set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, Selectors.PerformingUserId); + set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, nameof(PerformingUserId)); } /// public string PerformingDetails { get => _performingDetails; - set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, Selectors.PerformingDetails); + set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, nameof(PerformingDetails)); } /// public string PerformingIp { get => _performingIp; - set => SetPropertyValueAndDetectChanges(value, ref _performingIp, Selectors.PerformingIp); + set => SetPropertyValueAndDetectChanges(value, ref _performingIp, nameof(PerformingIp)); } /// @@ -64,31 +48,31 @@ namespace Umbraco.Core.Models } /// - public int AffectedUserId + public int AffectedUserId { get => _affectedUserId; - set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, Selectors.AffectedUserId); + set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, nameof(AffectedUserId)); } /// - public string AffectedDetails + public string AffectedDetails { get => _affectedDetails; - set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, Selectors.AffectedDetails); + set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, nameof(AffectedDetails)); } /// - public string EventType + public string EventType { get => _eventType; - set => SetPropertyValueAndDetectChanges(value, ref _eventType, Selectors.EventType); + set => SetPropertyValueAndDetectChanges(value, ref _eventType, nameof(EventType)); } /// - public string EventDetails + public string EventDetails { get => _eventDetails; - set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, Selectors.EventDetails); + set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, nameof(EventDetails)); } } } diff --git a/src/Umbraco.Core/Models/Consent.cs b/src/Umbraco.Core/Models/Consent.cs index 87dd9767a0..4f7543fd59 100644 --- a/src/Umbraco.Core/Models/Consent.cs +++ b/src/Umbraco.Core/Models/Consent.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -13,8 +12,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] internal class Consent : EntityBase, IConsent { - private static PropertySelectors _selector; - private bool _current; private string _source; private string _context; @@ -22,24 +19,11 @@ namespace Umbraco.Core.Models private ConsentState _state; private string _comment; - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo Current = ExpressionHelper.GetPropertyInfo(x => x.Current); - public readonly PropertyInfo Source = ExpressionHelper.GetPropertyInfo(x => x.Source); - public readonly PropertyInfo Context = ExpressionHelper.GetPropertyInfo(x => x.Context); - public readonly PropertyInfo Action = ExpressionHelper.GetPropertyInfo(x => x.Action); - public readonly PropertyInfo State = ExpressionHelper.GetPropertyInfo(x => x.State); - public readonly PropertyInfo Comment = ExpressionHelper.GetPropertyInfo(x => x.Comment); - } - - private static PropertySelectors Selectors => _selector ?? (_selector = new PropertySelectors()); - /// public bool Current { get => _current; - set => SetPropertyValueAndDetectChanges(value, ref _current, Selectors.Current); + set => SetPropertyValueAndDetectChanges(value, ref _current, nameof(Current)); } /// @@ -49,7 +33,7 @@ namespace Umbraco.Core.Models set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _source, Selectors.Source); + SetPropertyValueAndDetectChanges(value, ref _source, nameof(Source)); } } @@ -60,7 +44,7 @@ namespace Umbraco.Core.Models set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _context, Selectors.Context); + SetPropertyValueAndDetectChanges(value, ref _context, nameof(Context)); } } @@ -71,7 +55,7 @@ namespace Umbraco.Core.Models set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _action, Selectors.Action); + SetPropertyValueAndDetectChanges(value, ref _action, nameof(Action)); } } @@ -81,14 +65,14 @@ namespace Umbraco.Core.Models get => _state; // note: we probably should validate the state here, but since the // enum is [Flags] with many combinations, this could be expensive - set => SetPropertyValueAndDetectChanges(value, ref _state, Selectors.State); + set => SetPropertyValueAndDetectChanges(value, ref _state, nameof(State)); } /// public string Comment { get => _comment; - set => SetPropertyValueAndDetectChanges(value, ref _comment, Selectors.Comment); + set => SetPropertyValueAndDetectChanges(value, ref _comment, nameof(Comment)); } /// diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 0c679e5e70..c5930bf998 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Exceptions; @@ -15,16 +14,19 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Content : ContentBase, IContent { - private IContentType _contentType; private int? _templateId; private ContentScheduleCollection _schedule; private bool _published; private PublishedState _publishedState; - private ContentCultureInfosCollection _publishInfos; - private ContentCultureInfosCollection _publishInfosOrig; private HashSet _editedCultures; + private ContentCultureInfosCollection _publishInfos; - private static readonly Lazy Ps = new Lazy(); + #region Used for change tracking + + private (HashSet addedCultures, HashSet removedCultures, HashSet updatedCultures) _currentPublishCultureChanges; + private (HashSet addedCultures, HashSet removedCultures, HashSet updatedCultures) _previousPublishCultureChanges; + + #endregion /// /// Constructor for creating a Content object @@ -48,7 +50,7 @@ namespace Umbraco.Core.Models public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string culture = null) : base(name, parent, contentType, properties, culture) { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + if (contentType == null) throw new ArgumentNullException(nameof(contentType)); _publishedState = PublishedState.Unpublished; PublishedVersionId = 0; } @@ -75,20 +77,11 @@ namespace Umbraco.Core.Models public Content(string name, int parentId, IContentType contentType, PropertyCollection properties, string culture = null) : base(name, parentId, contentType, properties, culture) { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + if (contentType == null) throw new ArgumentNullException(nameof(contentType)); _publishedState = PublishedState.Unpublished; PublishedVersionId = 0; } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.TemplateId); - public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); - public readonly PropertyInfo ContentScheduleSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentSchedule); - public readonly PropertyInfo PublishCultureInfosSelector = ExpressionHelper.GetPropertyInfo>(x => x.PublishCultureInfos); - } - /// [DoNotClone] public ContentScheduleCollection ContentSchedule @@ -104,9 +97,9 @@ namespace Umbraco.Core.Models } set { - if(_schedule != null) + if (_schedule != null) _schedule.CollectionChanged -= ScheduleCollectionChanged; - SetPropertyValueAndDetectChanges(value, ref _schedule, Ps.Value.ContentScheduleSelector); + SetPropertyValueAndDetectChanges(value, ref _schedule, nameof(ContentSchedule)); if (_schedule != null) _schedule.CollectionChanged += ScheduleCollectionChanged; } @@ -119,7 +112,7 @@ namespace Umbraco.Core.Models /// private void ScheduleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.ContentScheduleSelector); + OnPropertyChanged(nameof(ContentSchedule)); } /// @@ -134,24 +127,24 @@ namespace Umbraco.Core.Models public int? TemplateId { get => _templateId; - set => SetPropertyValueAndDetectChanges(value, ref _templateId, Ps.Value.TemplateSelector); + set => SetPropertyValueAndDetectChanges(value, ref _templateId, nameof(TemplateId)); } - /// /// Gets or sets a value indicating whether this content item is published or not. /// + /// + /// the setter is should only be invoked from + /// - the ContentFactory when creating a content entity from a dto + /// - the ContentRepository when updating a content entity + /// [DataMember] public bool Published { get => _published; - - // the setter is internal and should only be invoked from - // - the ContentFactory when creating a content entity from a dto - // - the ContentRepository when updating a content entity - internal set + set { - SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); + SetPropertyValueAndDetectChanges(value, ref _published, nameof(Published)); _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; } } @@ -175,33 +168,31 @@ namespace Umbraco.Core.Models } [IgnoreDataMember] - public bool Edited { get; internal set; } - - /// - /// Gets the ContentType used by this content object - /// - [IgnoreDataMember] - public IContentType ContentType => _contentType; + public bool Edited { get; set; } /// [IgnoreDataMember] - public DateTime? PublishDate { get; internal set; } // set by persistence + public DateTime? PublishDate { get; set; } // set by persistence /// [IgnoreDataMember] - public int? PublisherId { get; internal set; } // set by persistence + public int? PublisherId { get; set; } // set by persistence /// [IgnoreDataMember] - public int? PublishTemplateId { get; internal set; } // set by persistence + public int? PublishTemplateId { get; set; } // set by persistence /// [IgnoreDataMember] - public string PublishName { get; internal set; } // set by persistence + public string PublishName { get; set; } // set by persistence /// [IgnoreDataMember] - public IEnumerable EditedCultures => CultureInfos.Keys.Where(IsCultureEdited); + public IEnumerable EditedCultures + { + get => CultureInfos.Keys.Where(IsCultureEdited); + set => _editedCultures = value == null ? null : new HashSet(value, StringComparer.OrdinalIgnoreCase); + } /// [IgnoreDataMember] @@ -213,32 +204,6 @@ namespace Umbraco.Core.Models // a non-available culture could not become published anyways => _publishInfos != null && _publishInfos.ContainsKey(culture); - /// - public bool WasCulturePublished(string culture) - // just check _publishInfosOrig - a copy of _publishInfos - // a non-available culture could not become published anyways - => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); - - // adjust dates to sync between version, cultures etc - // used by the repo when persisting - internal void AdjustDates(DateTime date) - { - foreach (var culture in PublishedCultures.ToList()) - { - if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos)) - continue; - - if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig) - && publishInfosOrig.Date == publishInfos.Date) - continue; - - _publishInfos.AddOrUpdate(culture, publishInfos.Name, date); - - if (CultureInfos.TryGetValue(culture, out var infos)) - SetCultureInfo(culture, infos.Name, date); - } - } - /// public bool IsCultureEdited(string culture) => IsCultureAvailable(culture) && // is available, and @@ -247,13 +212,29 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] - public IReadOnlyDictionary PublishCultureInfos => _publishInfos ?? NoInfos; + public ContentCultureInfosCollection PublishCultureInfos + { + get + { + if (_publishInfos != null) return _publishInfos; + _publishInfos = new ContentCultureInfosCollection(); + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + return _publishInfos; + } + set + { + if (_publishInfos != null) _publishInfos.CollectionChanged -= PublishNamesCollectionChanged; + _publishInfos = value; + if (_publishInfos != null) + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + } + } /// public string GetPublishName(string culture) { if (culture.IsNullOrWhiteSpace()) return PublishName; - if (!ContentTypeBase.VariesByCulture()) return null; + if (!ContentType.VariesByCulture()) return null; if (_publishInfos == null) return null; return _publishInfos.TryGetValue(culture, out var infos) ? infos.Name : null; } @@ -262,69 +243,9 @@ namespace Umbraco.Core.Models public DateTime? GetPublishDate(string culture) { if (culture.IsNullOrWhiteSpace()) return PublishDate; - if (!ContentTypeBase.VariesByCulture()) return null; + if (!ContentType.VariesByCulture()) return null; if (_publishInfos == null) return null; - return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null; - } - - // internal for repository - internal void SetPublishInfo(string culture, string name, DateTime date) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullOrEmptyException(nameof(name)); - - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - - if (_publishInfos == null) - { - _publishInfos = new ContentCultureInfosCollection(); - _publishInfos.CollectionChanged += PublishNamesCollectionChanged; - } - - _publishInfos.AddOrUpdate(culture, name, date); - } - - private void ClearPublishInfos() - { - _publishInfos = null; - } - - private void ClearPublishInfo(string culture) - { - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - - if (_publishInfos == null) return; - _publishInfos.Remove(culture); - if (_publishInfos.Count == 0) _publishInfos = null; - - // set the culture to be dirty - it's been modified - TouchCultureInfo(culture); - } - - // sets a publish edited - internal void SetCultureEdited(string culture) - { - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - if (_editedCultures == null) - _editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); - _editedCultures.Add(culture.ToLowerInvariant()); - } - - // sets all publish edited - internal void SetCultureEdited(IEnumerable cultures) - { - if (cultures == null) - { - _editedCultures = null; - } - else - { - var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase); - _editedCultures = editedCultures.Count > 0 ? editedCultures : null; - } + return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?)null; } /// @@ -332,103 +253,57 @@ namespace Umbraco.Core.Models /// private void PublishNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.PublishCultureInfosSelector); + OnPropertyChanged(nameof(PublishCultureInfos)); + + //we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too + //which would allows us to continue doing WasCulturePublished, but don't think we need it anymore + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + { + var cultureInfo = e.NewItems.Cast().First(); + if (_currentPublishCultureChanges.addedCultures == null) _currentPublishCultureChanges.addedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + if (_currentPublishCultureChanges.updatedCultures == null) _currentPublishCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + _currentPublishCultureChanges.addedCultures.Add(cultureInfo.Culture); + _currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture); + _currentPublishCultureChanges.removedCultures?.Remove(cultureInfo.Culture); + break; + } + case NotifyCollectionChangedAction.Remove: + { + //remove listening for changes + var cultureInfo = e.OldItems.Cast().First(); + if (_currentPublishCultureChanges.removedCultures == null) _currentPublishCultureChanges.removedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + _currentPublishCultureChanges.removedCultures.Add(cultureInfo.Culture); + _currentPublishCultureChanges.updatedCultures?.Remove(cultureInfo.Culture); + _currentPublishCultureChanges.addedCultures?.Remove(cultureInfo.Culture); + break; + } + case NotifyCollectionChangedAction.Replace: + { + //replace occurs when an Update occurs + var cultureInfo = e.NewItems.Cast().First(); + if (_currentPublishCultureChanges.updatedCultures == null) _currentPublishCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + _currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture); + break; + } + } } [IgnoreDataMember] - public int PublishedVersionId { get; internal set; } + public int PublishedVersionId { get; set; } [DataMember] - public bool Blueprint { get; internal set; } - - /// - public bool PublishCulture(string culture = "*") - { - culture = culture.NullOrWhiteSpaceAsNull(); - - // the variation should be supported by the content type properties - // if the content type is invariant, only '*' and 'null' is ok - // if the content type varies, everything is ok because some properties may be invariant - if (!ContentType.SupportsPropertyVariation(culture, "*", true)) - throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\"."); - - // the values we want to publish should be valid - if (ValidateProperties(culture).Any()) - return false; - - var alsoInvariant = false; - if (culture == "*") // all cultures - { - foreach (var c in AvailableCultures) - { - var name = GetCultureName(c); - if (string.IsNullOrWhiteSpace(name)) - return false; - SetPublishInfo(c, name, DateTime.Now); - } - } - else if (culture == null) // invariant culture - { - if (string.IsNullOrWhiteSpace(Name)) - return false; - // PublishName set by repository - nothing to do here - } - else // one single culture - { - var name = GetCultureName(culture); - if (string.IsNullOrWhiteSpace(name)) - return false; - SetPublishInfo(culture, name, DateTime.Now); - alsoInvariant = true; // we also want to publish invariant values - } - - // property.PublishValues only publishes what is valid, variation-wise - foreach (var property in Properties) - { - property.PublishValues(culture); - if (alsoInvariant) - property.PublishValues(null); - } - - _publishedState = PublishedState.Publishing; - return true; - } - - /// - public void UnpublishCulture(string culture = "*") - { - culture = culture.NullOrWhiteSpaceAsNull(); - - // the variation should be supported by the content type properties - if (!ContentType.SupportsPropertyVariation(culture, "*", true)) - throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\"."); - - if (culture == "*") // all cultures - ClearPublishInfos(); - else // one single culture - ClearPublishInfo(culture); - - // property.PublishValues only publishes what is valid, variation-wise - foreach (var property in Properties) - property.UnpublishValues(culture); - - _publishedState = PublishedState.Publishing; - } + public bool Blueprint { get; set; } /// /// Changes the for the current content object /// /// New ContentType for this content /// Leaves PropertyTypes intact after change - public void ChangeContentType(IContentType contentType) + internal void ChangeContentType(IContentType contentType) { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsurePropertyTypes(PropertyTypes); - - Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add - Properties.CollectionChanged += PropertiesChanged; + ChangeContentType(contentType, false); } /// @@ -437,45 +312,104 @@ namespace Umbraco.Core.Models /// /// New ContentType for this content /// Boolean indicating whether to clear PropertyTypes upon change - public void ChangeContentType(IContentType contentType, bool clearProperties) + internal void ChangeContentType(IContentType contentType, bool clearProperties) { - if(clearProperties) - { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsureCleanPropertyTypes(PropertyTypes); + ChangeContentType(new SimpleContentType(contentType)); - Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add - Properties.CollectionChanged += PropertiesChanged; - return; - } + if (clearProperties) + Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes); + else + Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); - ChangeContentType(contentType); + Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add + Properties.CollectionChanged += PropertiesChanged; + } + + public override void ResetWereDirtyProperties() + { + base.ResetWereDirtyProperties(); + _previousPublishCultureChanges.updatedCultures = null; + _previousPublishCultureChanges.removedCultures = null; + _previousPublishCultureChanges.addedCultures = null; } public override void ResetDirtyProperties(bool rememberDirty) { base.ResetDirtyProperties(rememberDirty); - - if (ContentType != null) - ContentType.ResetDirtyProperties(rememberDirty); + + if (rememberDirty) + { + _previousPublishCultureChanges.addedCultures = _currentPublishCultureChanges.addedCultures == null || _currentPublishCultureChanges.addedCultures.Count == 0 ? null : new HashSet(_currentPublishCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase); + _previousPublishCultureChanges.removedCultures = _currentPublishCultureChanges.removedCultures == null || _currentPublishCultureChanges.removedCultures.Count == 0 ? null : new HashSet(_currentPublishCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase); + _previousPublishCultureChanges.updatedCultures = _currentPublishCultureChanges.updatedCultures == null || _currentPublishCultureChanges.updatedCultures.Count == 0 ? null : new HashSet(_currentPublishCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase); + } + else + { + _previousPublishCultureChanges.addedCultures = null; + _previousPublishCultureChanges.removedCultures = null; + _previousPublishCultureChanges.updatedCultures = null; + } + _currentPublishCultureChanges.addedCultures?.Clear(); + _currentPublishCultureChanges.removedCultures?.Clear(); + _currentPublishCultureChanges.updatedCultures?.Clear(); // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; - // Make a copy of the _publishInfos, this is purely so that we can detect - // if this entity's previous culture publish state (regardless of the rememberDirty flag) - _publishInfosOrig = _publishInfos == null - ? null - : new ContentCultureInfosCollection(_publishInfos); - if (_publishInfos == null) return; foreach (var infos in _publishInfos) infos.ResetDirtyProperties(rememberDirty); } + /// + /// Overridden to check special keys. + public override bool IsPropertyDirty(string propertyName) + { + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); + return _currentPublishCultureChanges.addedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture); + return _currentPublishCultureChanges.removedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture); + return _currentPublishCultureChanges.updatedCultures?.Contains(culture) ?? false; + } + + return base.IsPropertyDirty(propertyName); + } + + /// + /// Overridden to check special keys. + public override bool WasPropertyDirty(string propertyName) + { + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); + return _previousPublishCultureChanges.addedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture); + return _previousPublishCultureChanges.removedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture); + return _previousPublishCultureChanges.updatedCultures?.Contains(culture) ?? false; + } + + return base.WasPropertyDirty(propertyName); + } + /// /// Creates a deep clone of the current entity with its identity and it's property identities reset /// @@ -499,14 +433,13 @@ namespace Umbraco.Core.Models var clonedContent = (Content)clone; - //need to manually clone this since it's not settable - clonedContent._contentType = (IContentType) ContentType.DeepClone(); + //fixme - need to reset change tracking bits //if culture infos exist then deal with event bindings if (clonedContent._publishInfos != null) { clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any - clonedContent._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone + clonedContent._publishInfos = (ContentCultureInfosCollection)_publishInfos.DeepClone(); //manually deep clone clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler } @@ -517,6 +450,14 @@ namespace Umbraco.Core.Models clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler } + + clonedContent._currentPublishCultureChanges.updatedCultures = null; + clonedContent._currentPublishCultureChanges.addedCultures = null; + clonedContent._currentPublishCultureChanges.removedCultures = null; + + clonedContent._previousPublishCultureChanges.updatedCultures = null; + clonedContent._previousPublishCultureChanges.addedCultures = null; + clonedContent._previousPublishCultureChanges.removedCultures = null; } } } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index ca1152a9a4..fbb68194b7 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; +using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; @@ -15,17 +15,31 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] + [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentType.Alias}")] public abstract class ContentBase : TreeEntityBase, IContentBase { - protected static readonly ContentCultureInfosCollection NoInfos = new ContentCultureInfosCollection(); - private static readonly Lazy Ps = new Lazy(); - private int _contentTypeId; - protected IContentTypeComposition ContentTypeBase; private int _writerId; private PropertyCollection _properties; private ContentCultureInfosCollection _cultureInfos; + internal IReadOnlyList AllPropertyTypes { get; } + + #region Used for change tracking + + private (HashSet addedCultures, HashSet removedCultures, HashSet updatedCultures) _currentCultureChanges; + private (HashSet addedCultures, HashSet removedCultures, HashSet updatedCultures) _previousCultureChanges; + + public static class ChangeTrackingPrefix + { + public const string UpdatedCulture = "_updatedCulture_"; + public const string ChangedCulture = "_changedCulture_"; + public const string PublishedCulture = "_publishedCulture_"; + public const string UnpublishedCulture = "_unpublishedCulture_"; + public const string AddedCulture = "_addedCulture_"; + public const string RemovedCulture = "_removedCulture_"; + } + + #endregion /// /// Initializes a new instance of the class. @@ -49,7 +63,7 @@ namespace Umbraco.Core.Models private ContentBase(string name, IContentTypeComposition contentType, PropertyCollection properties, string culture = null) { - ContentTypeBase = contentType ?? throw new ArgumentNullException(nameof(contentType)); + ContentType = contentType?.ToSimple() ?? throw new ArgumentNullException(nameof(contentType)); // initially, all new instances have Id = 0; // no identity @@ -59,53 +73,58 @@ namespace Umbraco.Core.Models _contentTypeId = contentType.Id; _properties = properties ?? throw new ArgumentNullException(nameof(properties)); - _properties.EnsurePropertyTypes(PropertyTypes); + _properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); + + //track all property types on this content type, these can never change during the lifetime of this single instance + //there is no real extra memory overhead of doing this since these property types are already cached on this object via the + //properties already. + AllPropertyTypes = new List(contentType.CompositionPropertyTypes); } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors + [IgnoreDataMember] + public ISimpleContentType ContentType { get; private set; } + + internal void ChangeContentType(ISimpleContentType contentType) { - public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); - public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); - public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); - public readonly PropertyInfo CultureInfosSelector = ExpressionHelper.GetPropertyInfo>(x => x.CultureInfos); + ContentType = contentType; + ContentTypeId = contentType.Id; } protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.PropertyCollectionSelector); + OnPropertyChanged(nameof(Properties)); } /// /// Id of the user who wrote/updated this entity /// [DataMember] - public virtual int WriterId + public int WriterId { get => _writerId; - set => SetPropertyValueAndDetectChanges(value, ref _writerId, Ps.Value.WriterSelector); + set => SetPropertyValueAndDetectChanges(value, ref _writerId, nameof(WriterId)); } [IgnoreDataMember] - public int VersionId { get; internal set; } + public int VersionId { get; set; } /// /// Integer Id of the default ContentType /// [DataMember] - public virtual int ContentTypeId + public int ContentTypeId { get { //There will be cases where this has not been updated to reflect the true content type ID. //This will occur when inserting new content. - if (_contentTypeId == 0 && ContentTypeBase != null && ContentTypeBase.HasIdentity) + if (_contentTypeId == 0 && ContentType != null) { - _contentTypeId = ContentTypeBase.Id; + _contentTypeId = ContentType.Id; } return _contentTypeId; } - protected set => SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); + private set => SetPropertyValueAndDetectChanges(value, ref _contentTypeId, nameof(ContentTypeId)); } /// @@ -116,30 +135,17 @@ namespace Umbraco.Core.Models /// [DataMember] [DoNotClone] - public virtual PropertyCollection Properties + public PropertyCollection Properties { get => _properties; set { + if (_properties != null) _properties.CollectionChanged -= PropertiesChanged; _properties = value; _properties.CollectionChanged += PropertiesChanged; } } - /// - /// Gets the enumeration of property groups for the entity. - /// TODO: remove this proxy method - /// - [IgnoreDataMember] - public IEnumerable PropertyGroups => ContentTypeBase.CompositionPropertyGroups; - - /// - /// Gets the numeration of property types for the entity. - /// TODO: remove this proxy method - /// - [IgnoreDataMember] - public IEnumerable PropertyTypes => ContentTypeBase.CompositionPropertyTypes; - #region Cultures // notes - common rules @@ -158,13 +164,29 @@ namespace Umbraco.Core.Models /// [DataMember] - public virtual IReadOnlyDictionary CultureInfos => _cultureInfos ?? NoInfos; + public ContentCultureInfosCollection CultureInfos + { + get + { + if (_cultureInfos != null) return _cultureInfos; + _cultureInfos = new ContentCultureInfosCollection(); + _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; + return _cultureInfos; + } + set + { + if (_cultureInfos != null) _cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; + _cultureInfos = value; + if (_cultureInfos != null) + _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; + } + } /// public string GetCultureName(string culture) { if (culture.IsNullOrWhiteSpace()) return Name; - if (!ContentTypeBase.VariesByCulture()) return null; + if (!ContentType.VariesByCulture()) return null; if (_cultureInfos == null) return null; return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null; } @@ -173,15 +195,15 @@ namespace Umbraco.Core.Models public DateTime? GetUpdateDate(string culture) { if (culture.IsNullOrWhiteSpace()) return null; - if (!ContentTypeBase.VariesByCulture()) return null; + if (!ContentType.VariesByCulture()) return null; if (_cultureInfos == null) return null; - return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null; + return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?)null; } /// public void SetCultureName(string name, string culture) { - if (ContentTypeBase.VariesByCulture()) // set on variant content type + if (ContentType.VariesByCulture()) // set on variant content type { if (culture.IsNullOrWhiteSpace()) // invariant is ok { @@ -193,7 +215,7 @@ namespace Umbraco.Core.Models } else // set { - SetCultureInfo(culture, name, DateTime.Now); + this.SetCultureInfo(culture, name, DateTime.Now); } } else // set on invariant content type @@ -205,13 +227,7 @@ namespace Umbraco.Core.Models } } - protected void ClearCultureInfos() - { - _cultureInfos?.Clear(); - _cultureInfos = null; - } - - protected void ClearCultureInfo(string culture) + private void ClearCultureInfo(string culture) { if (culture.IsNullOrWhiteSpace()) throw new ArgumentNullOrEmptyException(nameof(culture)); @@ -222,36 +238,44 @@ namespace Umbraco.Core.Models _cultureInfos = null; } - protected void TouchCultureInfo(string culture) - { - if (_cultureInfos == null || !_cultureInfos.TryGetValue(culture, out var infos)) return; - _cultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now); - } - - // internal for repository - internal void SetCultureInfo(string culture, string name, DateTime date) - { - if (name.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(name)); - - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - - if (_cultureInfos == null) - { - _cultureInfos = new ContentCultureInfosCollection(); - _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; - } - - _cultureInfos.AddOrUpdate(culture, name, date); - } - /// /// Handles culture infos collection changes. /// private void CultureInfosCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.CultureInfosSelector); + OnPropertyChanged(nameof(CultureInfos)); + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + { + var cultureInfo = e.NewItems.Cast().First(); + if (_currentCultureChanges.addedCultures == null) _currentCultureChanges.addedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + if (_currentCultureChanges.updatedCultures == null) _currentCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + _currentCultureChanges.addedCultures.Add(cultureInfo.Culture); + _currentCultureChanges.updatedCultures.Add(cultureInfo.Culture); + _currentCultureChanges.removedCultures?.Remove(cultureInfo.Culture); + break; + } + case NotifyCollectionChangedAction.Remove: + { + //remove listening for changes + var cultureInfo = e.OldItems.Cast().First(); + if (_currentCultureChanges.removedCultures == null) _currentCultureChanges.removedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + _currentCultureChanges.removedCultures.Add(cultureInfo.Culture); + _currentCultureChanges.updatedCultures?.Remove(cultureInfo.Culture); + _currentCultureChanges.addedCultures?.Remove(cultureInfo.Culture); + break; + } + case NotifyCollectionChangedAction.Replace: + { + //replace occurs when an Update occurs + var cultureInfo = e.NewItems.Cast().First(); + if (_currentCultureChanges.updatedCultures == null) _currentCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); + _currentCultureChanges.updatedCultures.Add(cultureInfo.Culture); + break; + } + } } #endregion @@ -259,11 +283,11 @@ namespace Umbraco.Core.Models #region Has, Get, Set, Publish Property Value /// - public virtual bool HasProperty(string propertyTypeAlias) + public bool HasProperty(string propertyTypeAlias) => Properties.Contains(propertyTypeAlias); /// - public virtual object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) + public object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) { return Properties.TryGetValue(propertyTypeAlias, out var property) ? property.GetValue(culture, segment, published) @@ -271,7 +295,7 @@ namespace Umbraco.Core.Models } /// - public virtual TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) + public TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) { if (!Properties.TryGetValue(propertyTypeAlias, out var property)) return default; @@ -281,121 +305,51 @@ namespace Umbraco.Core.Models } /// - public virtual void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null) + public void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null) { - if (Properties.Contains(propertyTypeAlias)) - { - Properties[propertyTypeAlias].SetValue(value, culture, segment); - return; - } - - var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (propertyType == null) + if (!Properties.TryGetValue(propertyTypeAlias, out var property)) throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); - var property = propertyType.CreateProperty(); property.SetValue(value, culture, segment); - Properties.Add(property); - } - #endregion - - #region Copy - - /// - public virtual void CopyFrom(IContent other, string culture = "*") - { - if (other.ContentTypeId != ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); - - culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull(); - - // the variation should be supported by the content type properties - // if the content type is invariant, only '*' and 'null' is ok - // if the content type varies, everything is ok because some properties may be invariant - if (!ContentTypeBase.SupportsPropertyVariation(culture, "*", true)) - throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentTypeBase.Alias}\" with variation \"{ContentTypeBase.Variations}\"."); - - // copying from the same Id and VersionPk - var copyingFromSelf = Id == other.Id && VersionId == other.VersionId; - var published = copyingFromSelf; - - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - - // clear all existing properties for the specified culture - foreach (var property in Properties) - { - // each property type may or may not support the variation - if (!property.PropertyType.SupportsVariation(culture, "*", wildcards: true)) - continue; - - foreach (var pvalue in property.Values) - if (property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) && - (culture == "*" || pvalue.Culture.InvariantEquals(culture))) - { - property.SetValue(null, pvalue.Culture, pvalue.Segment); - } - } - - // copy properties from 'other' - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) - { - if (!otherProperty.PropertyType.SupportsVariation(culture, "*", wildcards: true)) - continue; - - var alias = otherProperty.PropertyType.Alias; - foreach (var pvalue in otherProperty.Values) - { - if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) && - (culture == "*" || pvalue.Culture.InvariantEquals(culture))) - { - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - SetValue(alias, value, pvalue.Culture, pvalue.Segment); - } - } - } - - // copy names, too - - if (culture == "*") - ClearCultureInfos(); - - if (culture == null || culture == "*") - Name = other.Name; - - foreach (var (otherCulture, otherInfos) in other.CultureInfos) - { - if (culture == "*" || culture == otherCulture) - SetCultureName(otherInfos.Name, otherCulture); - } - } - - #endregion - - #region Validation - - /// - public virtual Property[] ValidateProperties(string culture = "*") - { - var alsoInvariant = culture != null && culture != "*"; - - return Properties.Where(x => // select properties... - x.PropertyType.SupportsVariation(culture, "*", true) && // that support the variation - (!x.IsValid(culture) || (alsoInvariant && !x.IsValid(null)))) // and are not valid - .ToArray(); + //bump the culture to be flagged for updating + this.TouchCulture(culture); } #endregion #region Dirty + public override void ResetWereDirtyProperties() + { + base.ResetWereDirtyProperties(); + _previousCultureChanges.addedCultures = null; + _previousCultureChanges.removedCultures = null; + _previousCultureChanges.updatedCultures = null; + } + /// /// Overridden to include user properties. public override void ResetDirtyProperties(bool rememberDirty) { base.ResetDirtyProperties(rememberDirty); + if (rememberDirty) + { + _previousCultureChanges.addedCultures = _currentCultureChanges.addedCultures == null || _currentCultureChanges.addedCultures.Count == 0 ? null : new HashSet(_currentCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase); + _previousCultureChanges.removedCultures = _currentCultureChanges.removedCultures == null || _currentCultureChanges.removedCultures.Count == 0 ? null : new HashSet(_currentCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase); + _previousCultureChanges.updatedCultures = _currentCultureChanges.updatedCultures == null || _currentCultureChanges.updatedCultures.Count == 0 ? null : new HashSet(_currentCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase); + } + else + { + _previousCultureChanges.addedCultures = null; + _previousCultureChanges.removedCultures = null; + _previousCultureChanges.updatedCultures = null; + } + _currentCultureChanges.addedCultures?.Clear(); + _currentCultureChanges.removedCultures?.Clear(); + _currentCultureChanges.updatedCultures?.Clear(); + // also reset dirty changes made to user's properties foreach (var prop in Properties) prop.ResetDirtyProperties(rememberDirty); @@ -444,6 +398,23 @@ namespace Umbraco.Core.Models if (base.IsPropertyDirty(propertyName)) return true; + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); + return _currentCultureChanges.addedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.RemovedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.RemovedCulture); + return _currentCultureChanges.removedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.UpdatedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UpdatedCulture); + return _currentCultureChanges.updatedCultures?.Contains(culture) ?? false; + } + return Properties.Contains(propertyName) && Properties[propertyName].IsDirty(); } @@ -454,6 +425,23 @@ namespace Umbraco.Core.Models if (base.WasPropertyDirty(propertyName)) return true; + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); + return _previousCultureChanges.addedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.RemovedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.RemovedCulture); + return _previousCultureChanges.removedCultures?.Contains(culture) ?? false; + } + if (propertyName.StartsWith(ChangeTrackingPrefix.UpdatedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UpdatedCulture); + return _previousCultureChanges.updatedCultures?.Contains(culture) ?? false; + } + return Properties.Contains(propertyName) && Properties[propertyName].WasDirty(); } @@ -487,11 +475,14 @@ namespace Umbraco.Core.Models var clonedContent = (ContentBase)clone; + //need to manually clone this since it's not settable + clonedContent.ContentType = ContentType; + //if culture infos exist then deal with event bindings if (clonedContent._cultureInfos != null) { clonedContent._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any - clonedContent._cultureInfos = (ContentCultureInfosCollection) _cultureInfos.DeepClone(); //manually deep clone + clonedContent._cultureInfos = (ContentCultureInfosCollection)_cultureInfos.DeepClone(); //manually deep clone clonedContent._cultureInfos.CollectionChanged += clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler } @@ -499,9 +490,17 @@ namespace Umbraco.Core.Models if (clonedContent._properties != null) { clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any - clonedContent._properties = (PropertyCollection) _properties.DeepClone(); //manually deep clone + clonedContent._properties = (PropertyCollection)_properties.DeepClone(); //manually deep clone clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler } + + clonedContent._currentCultureChanges.updatedCultures = null; + clonedContent._currentCultureChanges.addedCultures = null; + clonedContent._currentCultureChanges.removedCultures = null; + + clonedContent._previousCultureChanges.updatedCultures = null; + clonedContent._previousCultureChanges.addedCultures = null; + clonedContent._previousCultureChanges.removedCultures = null; } } } diff --git a/src/Umbraco.Core/Models/ContentCultureInfos.cs b/src/Umbraco.Core/Models/ContentCultureInfos.cs index f51e3a275a..c8c4bea56a 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfos.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfos.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; @@ -13,7 +12,6 @@ namespace Umbraco.Core.Models { private DateTime _date; private string _name; - private static readonly Lazy Ps = new Lazy(); /// /// Initializes a new instance of the class. @@ -46,7 +44,7 @@ namespace Umbraco.Core.Models public string Name { get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -55,7 +53,7 @@ namespace Umbraco.Core.Models public DateTime Date { get => _date; - set => SetPropertyValueAndDetectChanges(value, ref _date, Ps.Value.DateSelector); + set => SetPropertyValueAndDetectChanges(value, ref _date, nameof(Date)); } /// @@ -102,12 +100,5 @@ namespace Umbraco.Core.Models Deconstruct(out culture, out name); date = Date; } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo DateSelector = ExpressionHelper.GetPropertyInfo(x => x.Date); - } } } diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs index 82b0ba6475..52f6f9adb6 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs @@ -17,20 +17,6 @@ namespace Umbraco.Core.Models public ContentCultureInfosCollection() : base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase) { } - - /// - /// Initializes a new instance of the class with items. - /// - public ContentCultureInfosCollection(IEnumerable items) - : base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase) - { - // make sure to add *copies* and not the original items, - // as items can be modified by AddOrUpdate, and therefore - // the new collection would be impacted by changes made - // to the old collection - foreach (var item in items) - Add(new ContentCultureInfos(item)); - } /// /// Adds or updates a instance. @@ -53,7 +39,7 @@ namespace Umbraco.Core.Models Name = name, Date = date }); - } + } } /// diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs new file mode 100644 index 0000000000..c0e4cd7032 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Collections; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models +{ + /// + /// Extension methods used to manipulate content variations by the document repository + /// + internal static class ContentRepositoryExtensions + { + /// + /// Gets the cultures that have been flagged for unpublishing. + /// + /// Gets cultures for which content.UnpublishCulture() has been invoked. + public static IReadOnlyList GetCulturesUnpublishing(this IContent content) + { + if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos")) + return Array.Empty(); + + var culturesUnpublishing = content.CultureInfos.Values + .Where(x => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + x.Culture)) + .Select(x => x.Culture); + + return culturesUnpublishing.ToList(); + } + + /// + /// Copies values from another document. + /// + public static void CopyFrom(this IContent content, IContent other, string culture = "*") + { + if (other.ContentTypeId != content.ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + // if the content type is invariant, only '*' and 'null' is ok + // if the content type varies, everything is ok because some properties may be invariant + if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + + // copying from the same Id and VersionPk + var copyingFromSelf = content.Id == other.Id && content.VersionId == other.VersionId; + var published = copyingFromSelf; + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties for the specified culture + foreach (var property in content.Properties) + { + // each property type may or may not support the variation + if (!property.PropertyType.SupportsVariation(culture, "*", wildcards: true)) + continue; + + foreach (var pvalue in property.Values) + if (property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) && + (culture == "*" || pvalue.Culture.InvariantEquals(culture))) + { + property.SetValue(null, pvalue.Culture, pvalue.Segment); + } + } + + // copy properties from 'other' + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + if (!otherProperty.PropertyType.SupportsVariation(culture, "*", wildcards: true)) + continue; + + var alias = otherProperty.PropertyType.Alias; + foreach (var pvalue in otherProperty.Values) + { + if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) && + (culture == "*" || pvalue.Culture.InvariantEquals(culture))) + { + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + content.SetValue(alias, value, pvalue.Culture, pvalue.Segment); + } + } + } + + // copy names, too + + if (culture == "*") + { + content.CultureInfos.Clear(); + content.CultureInfos = null; + } + + if (culture == null || culture == "*") + content.Name = other.Name; + + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in other.CultureInfos) + { + if (culture == "*" || culture == cultureInfo.Culture) + content.SetCultureName(cultureInfo.Name, cultureInfo.Culture); + } + } + + public static void SetPublishInfo(this IContent content, string culture, string name, DateTime date) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullOrEmptyException(nameof(name)); + + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + content.PublishCultureInfos.AddOrUpdate(culture, name, date); + } + + /// + /// Used to synchronize all culture dates to the same date if they've been modified + /// + /// + /// + /// + /// This is so that in an operation where (for example) 2 languages are updates like french and english, it is possible that + /// these dates assigned to them differ by a couple of Ticks, but we need to ensure they are persisted at the exact same time. + /// + public static void AdjustDates(this IContent content, DateTime date) + { + foreach (var culture in content.PublishedCultures.ToList()) + { + if (!content.PublishCultureInfos.TryGetValue(culture, out var publishInfos)) + continue; + + // if it's not dirty, it means it hasn't changed so there's nothing to adjust + if (!publishInfos.IsDirty()) + continue; + + content.PublishCultureInfos.AddOrUpdate(culture, publishInfos.Name, date); + + if (content.CultureInfos.TryGetValue(culture, out var infos)) + SetCultureInfo(content, culture, infos.Name, date); + } + } + + // sets the edited cultures on the content + public static void SetCultureEdited(this IContent content, IEnumerable cultures) + { + if (cultures == null) + content.EditedCultures = null; + else + { + var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase); + content.EditedCultures = editedCultures.Count > 0 ? editedCultures : null; + } + } + + public static void SetCultureInfo(this IContentBase content, string culture, string name, DateTime date) + { + if (name.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(name)); + + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + content.CultureInfos.AddOrUpdate(culture, name, date); + } + + /// + /// Sets the publishing values for names and properties. + /// + /// + /// + /// A value indicating whether it was possible to publish the names and values for the specified + /// culture(s). The method may fail if required names are not set, but it does NOT validate property data + public static bool PublishCulture(this IContent content, string culture = "*") + { + culture = culture.NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + // if the content type is invariant, only '*' and 'null' is ok + // if the content type varies, everything is ok because some properties may be invariant + if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + + var alsoInvariant = false; + if (culture == "*") // all cultures + { + foreach (var c in content.AvailableCultures) + { + var name = content.GetCultureName(c); + if (string.IsNullOrWhiteSpace(name)) + return false; + content.SetPublishInfo(c, name, DateTime.Now); + } + } + else if (culture == null) // invariant culture + { + if (string.IsNullOrWhiteSpace(content.Name)) + return false; + // PublishName set by repository - nothing to do here + } + else // one single culture + { + var name = content.GetCultureName(culture); + if (string.IsNullOrWhiteSpace(name)) + return false; + content.SetPublishInfo(culture, name, DateTime.Now); + alsoInvariant = true; // we also want to publish invariant values + } + + // property.PublishValues only publishes what is valid, variation-wise + foreach (var property in content.Properties) + { + property.PublishValues(culture); + if (alsoInvariant) + property.PublishValues(null); + } + + content.PublishedState = PublishedState.Publishing; + return true; + } + + public static void UnpublishCulture(this IContent content, string culture = "*") + { + culture = culture.NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + + if (culture == "*") // all cultures + content.ClearPublishInfos(); + else // one single culture + content.ClearPublishInfo(culture); + + // property.PublishValues only publishes what is valid, variation-wise + foreach (var property in content.Properties) + property.UnpublishValues(culture); + + content.PublishedState = PublishedState.Publishing; + } + + public static void ClearPublishInfos(this IContent content) + { + content.PublishCultureInfos = null; + } + + public static void ClearPublishInfo(this IContent content, string culture) + { + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + content.PublishCultureInfos.Remove(culture); + + // set the culture to be dirty - it's been modified + content.TouchCulture(culture); + } + + /// + /// Updates a culture date, if the culture exists. + /// + public static void TouchCulture(this IContentBase content, string culture) + { + if (culture.IsNullOrWhiteSpace()) return; + if (!content.CultureInfos.TryGetValue(culture, out var infos)) return; + content.CultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now); + } + } +} diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 4b9831682c..453670253e 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -13,8 +13,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class ContentType : ContentTypeCompositionBase, IContentType { - private static readonly Lazy Ps = new Lazy(); - public const bool IsPublishingConst = true; + public const bool SupportsPublishingConst = true; private int _defaultTemplate; private IEnumerable _allowedTemplates; @@ -43,19 +42,15 @@ namespace Umbraco.Core.Models } /// - public override bool IsPublishing => IsPublishingConst; + public override ISimpleContentType ToSimple() => new SimpleContentType(this); - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); - public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + /// + public override bool SupportsPublishing => SupportsPublishingConst; - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>( - (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), - templates => templates.GetHashCode()); - } + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>( + (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), + templates => templates.GetHashCode()); /// /// Gets or sets the alias of the default Template. @@ -75,8 +70,8 @@ namespace Umbraco.Core.Models [DataMember] internal int DefaultTemplateId { - get { return _defaultTemplate; } - set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); } + get => _defaultTemplate; + set => SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, nameof(DefaultTemplateId)); } /// @@ -91,7 +86,7 @@ namespace Umbraco.Core.Models get => _allowedTemplates; set { - SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer); + SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, nameof(AllowedTemplates), TemplateComparer); if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false) DefaultTemplateId = 0; @@ -105,8 +100,8 @@ namespace Umbraco.Core.Models /// True if AllowedTemplates contains the templateId else False public bool IsAllowedTemplate(int templateId) { - return AllowedTemplates == null - ? false + return AllowedTemplates == null + ? false : AllowedTemplates.Any(t => t.Id == templateId); } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 3da2838d0e..ed8a098299 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; @@ -18,8 +17,6 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase { - private static readonly Lazy Ps = new Lazy(); - private string _alias; private string _description; private string _icon = "icon-folder"; @@ -43,7 +40,7 @@ namespace Umbraco.Core.Models // actually OK as IsPublishing is constant // ReSharper disable once VirtualMemberCallInConstructor - _noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing); + _noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing); _noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged; _variations = ContentVariation.Nothing; @@ -64,14 +61,16 @@ namespace Umbraco.Core.Models // actually OK as IsPublishing is constant // ReSharper disable once VirtualMemberCallInConstructor - _noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing); + _noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing); _noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged; _variations = ContentVariation.Nothing; } + public abstract ISimpleContentType ToSimple(); + /// - /// Gets a value indicating whether the content type is publishing. + /// Gets a value indicating whether the content type supports publishing. /// /// /// A publishing content type supports draft and published values for properties. @@ -81,39 +80,22 @@ namespace Umbraco.Core.Models /// the draft or published value of a property returns the same thing, and publishing /// a value property has no effect. /// - public abstract bool IsPublishing { get; } + public abstract bool SupportsPublishing { get; } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); - public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); - public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); - public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); - public readonly PropertyInfo IsElementSelector = ExpressionHelper.GetPropertyInfo(x => x.IsElement); - public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); - public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); - public readonly PropertyInfo PropertyGroupsSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); - public readonly PropertyInfo PropertyTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); - public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); - public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> ContentTypeSortComparer = - new DelegateEqualityComparer>( - (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), - sorts => sorts.GetHashCode()); - } + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> ContentTypeSortComparer = + new DelegateEqualityComparer>( + (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), + sorts => sorts.GetHashCode()); protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.PropertyGroupsSelector); + OnPropertyChanged(nameof(PropertyGroups)); } protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.PropertyTypesSelector); + OnPropertyChanged(nameof(PropertyTypes)); } /// @@ -126,7 +108,7 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges( value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias, - Ps.Value.AliasSelector); + nameof(Alias)); } /// @@ -136,7 +118,7 @@ namespace Umbraco.Core.Models public string Description { get => _description; - set => SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); + set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description)); } /// @@ -146,7 +128,7 @@ namespace Umbraco.Core.Models public string Icon { get => _icon; - set => SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); + set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon)); } /// @@ -156,7 +138,7 @@ namespace Umbraco.Core.Models public string Thumbnail { get => _thumbnail; - set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); + set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, nameof(Thumbnail)); } /// @@ -166,7 +148,7 @@ namespace Umbraco.Core.Models public bool AllowedAsRoot { get => _allowedAsRoot; - set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); + set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, nameof(AllowedAsRoot)); } /// @@ -179,7 +161,7 @@ namespace Umbraco.Core.Models public bool IsContainer { get => _isContainer; - set => SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); + set => SetPropertyValueAndDetectChanges(value, ref _isContainer, nameof(IsContainer)); } /// @@ -187,7 +169,7 @@ namespace Umbraco.Core.Models public bool IsElement { get => _isElement; - set => SetPropertyValueAndDetectChanges(value, ref _isElement, Ps.Value.IsElementSelector); + set => SetPropertyValueAndDetectChanges(value, ref _isElement, nameof(IsElement)); } /// @@ -197,8 +179,8 @@ namespace Umbraco.Core.Models public IEnumerable AllowedContentTypes { get => _allowedContentTypes; - set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, - Ps.Value.ContentTypeSortComparer); + set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, nameof(AllowedContentTypes), + ContentTypeSortComparer); } /// @@ -207,7 +189,7 @@ namespace Umbraco.Core.Models public virtual ContentVariation Variations { get => _variations; - set => SetPropertyValueAndDetectChanges(value, ref _variations, Ps.Value.VaryBy); + set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations)); } /// @@ -275,7 +257,7 @@ namespace Umbraco.Core.Models { if (_noGroupPropertyTypes != null) _noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; - _noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing, value); + _noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing, value); _noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged; PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } @@ -295,7 +277,7 @@ namespace Umbraco.Core.Models private set { _hasPropertyTypeBeenRemoved = value; - OnPropertyChanged(Ps.Value.HasPropertyTypeBeenRemovedSelector); + OnPropertyChanged(nameof(HasPropertyTypeBeenRemoved)); } } @@ -388,7 +370,7 @@ namespace Umbraco.Core.Models if (!HasPropertyTypeBeenRemoved) { HasPropertyTypeBeenRemoved = true; - OnPropertyChanged(Ps.Value.PropertyTypesSelector); + OnPropertyChanged(nameof(PropertyTypes)); } break; } @@ -400,7 +382,7 @@ namespace Umbraco.Core.Models if (!HasPropertyTypeBeenRemoved) { HasPropertyTypeBeenRemoved = true; - OnPropertyChanged(Ps.Value.PropertyTypesSelector); + OnPropertyChanged(nameof(PropertyTypes)); } } } @@ -424,7 +406,7 @@ namespace Umbraco.Core.Models // actually remove the group PropertyGroups.RemoveItem(propertyGroupName); - OnPropertyChanged(Ps.Value.PropertyGroupsSelector); + OnPropertyChanged(nameof(PropertyGroups)); } /// diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 08b9f74802..7497c100bc 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Exceptions; @@ -14,8 +13,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition { - private static readonly Lazy Ps = new Lazy(); - private List _contentTypeComposition = new List(); internal List RemovedContentTypeKeyTracker = new List(); @@ -32,13 +29,6 @@ namespace Umbraco.Core.Models AddContentType(parent); } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo ContentTypeCompositionSelector = - ExpressionHelper.GetPropertyInfo>(x => x.ContentTypeComposition); - } - /// /// Gets or sets the content types that compose this content type. /// @@ -49,7 +39,7 @@ namespace Umbraco.Core.Models set { _contentTypeComposition = value.ToList(); - OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); + OnPropertyChanged(nameof(ContentTypeComposition)); } } @@ -161,7 +151,7 @@ namespace Umbraco.Core.Models throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); _contentTypeComposition.Add(contentType); - OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); + OnPropertyChanged(nameof(ContentTypeComposition)); return true; } return false; @@ -187,7 +177,7 @@ namespace Umbraco.Core.Models if (compositionIdsToRemove.Any()) RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove); - OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); + OnPropertyChanged(nameof(ContentTypeComposition)); return _contentTypeComposition.Remove(contentTypeComposition); } return false; @@ -236,7 +226,7 @@ namespace Umbraco.Core.Models return null; // create the new group - var group = new PropertyGroup(IsPublishing) { Name = name, SortOrder = 0 }; + var group = new PropertyGroup(SupportsPublishing) { Name = name, SortOrder = 0 }; // check if it is inherited - there might be more than 1 but we want the 1st, to // reuse its sort order - if there are more than 1 and they have different sort diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index 4f0d0d6c31..8745e6dbec 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Runtime.Serialization; using Newtonsoft.Json; using Umbraco.Core.Models.Entities; @@ -15,8 +14,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class DataType : TreeEntityBase, IDataType { - private static PropertySelectors _selectors; - private IDataEditor _editor; private ValueStorageType _databaseType; private object _configuration; @@ -35,15 +32,6 @@ namespace Umbraco.Core.Models Configuration = _editor.GetConfigurationEditor().DefaultConfigurationObject; } - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - - private class PropertySelectors - { - public readonly PropertyInfo Editor = ExpressionHelper.GetPropertyInfo(x => x.Editor); - public readonly PropertyInfo DatabaseType = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); - public readonly PropertyInfo Configuration = ExpressionHelper.GetPropertyInfo(x => x.Configuration); - } - /// [IgnoreDataMember] public IDataEditor Editor @@ -53,7 +41,7 @@ namespace Umbraco.Core.Models { // ignore if no change if (_editor.Alias == value.Alias) return; - OnPropertyChanged(Selectors.Editor); + OnPropertyChanged(nameof(Editor)); // try to map the existing configuration to the new configuration // simulate saving to db and reloading (ie go via json) @@ -73,7 +61,7 @@ namespace Umbraco.Core.Models public ValueStorageType DatabaseType { get => _databaseType; - set => SetPropertyValueAndDetectChanges(value, ref _databaseType, Selectors.DatabaseType); + set => SetPropertyValueAndDetectChanges(value, ref _databaseType, nameof(DatabaseType)); } /// @@ -124,7 +112,7 @@ namespace Umbraco.Core.Models _configurationJson = null; // it's always a change - OnPropertyChanged(Selectors.Configuration); + OnPropertyChanged(nameof(Configuration)); } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 919f1b0a1f..c23e8f86a4 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -30,20 +29,11 @@ namespace Umbraco.Core.Models _translations = new List(); } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); - public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> DictionaryTranslationComparer = - new DelegateEqualityComparer>( - (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), - enumerable => enumerable.GetHashCode()); - } + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> DictionaryTranslationComparer = + new DelegateEqualityComparer>( + (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), + enumerable => enumerable.GetHashCode()); /// /// Gets or Sets the Parent Id of the Dictionary Item @@ -52,7 +42,7 @@ namespace Umbraco.Core.Models public Guid? ParentId { get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); } } /// @@ -62,7 +52,7 @@ namespace Umbraco.Core.Models public string ItemKey { get { return _itemKey; } - set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); } + set { SetPropertyValueAndDetectChanges(value, ref _itemKey, nameof(ItemKey)); } } /// @@ -84,8 +74,8 @@ namespace Umbraco.Core.Models } } - SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, - Ps.Value.DictionaryTranslationComparer); + SetPropertyValueAndDetectChanges(asArray, ref _translations, nameof(Translations), + DictionaryTranslationComparer); } } } diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index c3b5a8a3b2..afb4063b97 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -1,8 +1,6 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { @@ -50,14 +48,6 @@ namespace Umbraco.Core.Models Key = uniqueId; } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - } - /// /// Gets or sets the for the translation /// @@ -84,7 +74,7 @@ namespace Umbraco.Core.Models } set { - SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); + SetPropertyValueAndDetectChanges(value, ref _language, nameof(Language)); _languageId = _language == null ? -1 : _language.Id; } } @@ -101,7 +91,7 @@ namespace Umbraco.Core.Models public string Value { get { return _value; } - set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } + set { SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); } } protected override void PerformDeepClone(object clone) diff --git a/src/Umbraco.Core/Models/Entities/BeingDirty.cs b/src/Umbraco.Core/Models/Entities/BeingDirty.cs index ec447a62dc..92b4ab5c4b 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirty.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirty.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Reflection; namespace Umbraco.Core.Models.Entities { @@ -19,19 +18,19 @@ namespace Umbraco.Core.Models.Entities /// The type of the value. /// The new value. /// A reference to the value to set. - /// The property selector. + /// The property name. /// A comparer to compare property values. - public new void SetPropertyValueAndDetectChanges(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer comparer = null) + public new void SetPropertyValueAndDetectChanges(T value, ref T valueRef, string propertyName, IEqualityComparer comparer = null) { - base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertySelector, comparer); + base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertyName, comparer); } /// /// Registers that a property has changed. /// - public new void OnPropertyChanged(PropertyInfo propertySelector) + public new void OnPropertyChanged(string propertyName) { - base.OnPropertyChanged(propertySelector); + base.OnPropertyChanged(propertyName); } } } diff --git a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs index 711b7c9b9f..a1f4bad9a1 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models.Entities @@ -66,7 +65,7 @@ namespace Umbraco.Core.Models.Entities } /// - public void ResetWereDirtyProperties() + public virtual void ResetWereDirtyProperties() { // note: cannot .Clear() because when memberwise-cloning this will be the SAME // instance as the one on the clone, so we need to create a new instance. @@ -108,7 +107,7 @@ namespace Umbraco.Core.Models.Entities /// /// Registers that a property has changed. /// - protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) + protected virtual void OnPropertyChanged(string propertyName) { if (_withChanges == false) return; @@ -116,9 +115,9 @@ namespace Umbraco.Core.Models.Entities if (_currentChanges == null) _currentChanges = new Dictionary(); - _currentChanges[propertyInfo.Name] = true; + _currentChanges[propertyName] = true; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// @@ -143,9 +142,9 @@ namespace Umbraco.Core.Models.Entities /// The type of the value. /// The new value. /// A reference to the value to set. - /// The property selector. + /// The property name. /// A comparer to compare property values. - protected void SetPropertyValueAndDetectChanges(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer comparer = null) + protected void SetPropertyValueAndDetectChanges(T value, ref T valueRef, string propertyName, IEqualityComparer comparer = null) { if (comparer == null) { @@ -165,7 +164,7 @@ namespace Umbraco.Core.Models.Entities // handle change if (changed) - OnPropertyChanged(propertySelector); + OnPropertyChanged(propertyName); } /// @@ -174,17 +173,17 @@ namespace Umbraco.Core.Models.Entities /// The type of the value. /// The new value. /// The original value. - /// The property selector. + /// The property name. /// A comparer to compare property values. /// A value indicating whether we know values have changed and no comparison is required. - protected void DetectChanges(T value, T orig, PropertyInfo propertySelector, IEqualityComparer comparer, bool changed) + protected void DetectChanges(T value, T orig, string propertyName, IEqualityComparer comparer, bool changed) { // compare values changed = _withChanges && (changed || !comparer.Equals(orig, value)); // handle change if (changed) - OnPropertyChanged(propertySelector); + OnPropertyChanged(propertyName); } #endregion diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs index 6c9bf3d711..43837fa776 100644 --- a/src/Umbraco.Core/Models/Entities/EntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models.Entities @@ -17,23 +16,12 @@ namespace Umbraco.Core.Models.Entities public Guid InstanceId = Guid.NewGuid(); #endif - private static readonly Lazy Ps = new Lazy(); - private bool _hasIdentity; private int _id; private Guid _key; private DateTime _createDate; private DateTime _updateDate; - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); - public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); - public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); - } - /// [DataMember] public int Id @@ -41,7 +29,7 @@ namespace Umbraco.Core.Models.Entities get => _id; set { - SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); + SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id)); _hasIdentity = value != 0; } } @@ -57,7 +45,7 @@ namespace Umbraco.Core.Models.Entities _key = Guid.NewGuid(); return _key; } - set => SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); + set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key)); } /// @@ -65,7 +53,7 @@ namespace Umbraco.Core.Models.Entities public DateTime CreateDate { get => _createDate; - set => SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); + set => SetPropertyValueAndDetectChanges(value, ref _createDate, nameof(CreateDate)); } /// @@ -73,7 +61,7 @@ namespace Umbraco.Core.Models.Entities public DateTime UpdateDate { get => _updateDate; - set => SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); + set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); } /// diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs index 60e06c4977..937d7ab0fc 100644 --- a/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models.Entities @@ -9,9 +8,6 @@ namespace Umbraco.Core.Models.Entities /// public abstract class TreeEntityBase : EntityBase, ITreeEntity { - private static PropertySelectors _selectors; - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - private string _name; private int _creatorId; private int _parentId; @@ -22,23 +18,12 @@ namespace Umbraco.Core.Models.Entities private int _sortOrder; private bool _trashed; - private class PropertySelectors - { - public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo CreatorId = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - public readonly PropertyInfo ParentId = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo Level = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo Path = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo Trashed = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - } - /// [DataMember] public string Name { get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -46,7 +31,7 @@ namespace Umbraco.Core.Models.Entities public int CreatorId { get => _creatorId; - set => SetPropertyValueAndDetectChanges(value, ref _creatorId, Selectors.CreatorId); + set => SetPropertyValueAndDetectChanges(value, ref _creatorId, nameof(CreatorId)); } /// @@ -72,7 +57,7 @@ namespace Umbraco.Core.Models.Entities { if (value == 0) throw new ArgumentException("Value cannot be zero.", nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _parentId, Selectors.ParentId); + SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); _hasParentId = true; _parent = null; } @@ -83,7 +68,7 @@ namespace Umbraco.Core.Models.Entities { _hasParentId = false; _parent = parent; - OnPropertyChanged(Selectors.ParentId); + OnPropertyChanged(nameof(ParentId)); } /// @@ -91,7 +76,7 @@ namespace Umbraco.Core.Models.Entities public int Level { get => _level; - set => SetPropertyValueAndDetectChanges(value, ref _level, Selectors.Level); + set => SetPropertyValueAndDetectChanges(value, ref _level, nameof(Level)); } /// @@ -99,7 +84,7 @@ namespace Umbraco.Core.Models.Entities public string Path { get => _path; - set => SetPropertyValueAndDetectChanges(value, ref _path, Selectors.Path); + set => SetPropertyValueAndDetectChanges(value, ref _path, nameof(Path)); } /// @@ -107,7 +92,7 @@ namespace Umbraco.Core.Models.Entities public int SortOrder { get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder); + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); } /// @@ -115,7 +100,7 @@ namespace Umbraco.Core.Models.Entities public bool Trashed { get => _trashed; - set => SetPropertyValueAndDetectChanges(value, ref _trashed, Selectors.Trashed); + set => SetPropertyValueAndDetectChanges(value, ref _trashed, nameof(Trashed)); } } } diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 2f8e021f4c..845da4d053 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -1,9 +1,5 @@ using System; -using System.IO; -using System.Reflection; using System.Runtime.Serialization; -using System.Text; -using Umbraco.Core.IO; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -33,14 +29,6 @@ namespace Umbraco.Core.Models _content = getFileContent != null ? null : string.Empty; } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - } - private string _alias; private string _name; @@ -96,7 +84,7 @@ namespace Umbraco.Core.Models _alias = null; _name = null; - SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); + SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, nameof(Path)); } } @@ -138,7 +126,7 @@ namespace Umbraco.Core.Models { SetPropertyValueAndDetectChanges( value ?? string.Empty, // cannot set to null - ref _content, Ps.Value.ContentSelector); + ref _content, nameof(Content)); } } diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 4363324c8d..6990a7f7da 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -25,51 +25,46 @@ namespace Umbraco.Core.Models /// /// Gets a value indicating whether the content is published. /// - bool Published { get; } + bool Published { get; set; } - PublishedState PublishedState { get; } + PublishedState PublishedState { get; set; } /// /// Gets a value indicating whether the content has been edited. /// - bool Edited { get; } + bool Edited { get; set; } /// /// Gets the published version identifier. /// - int PublishedVersionId { get; } + int PublishedVersionId { get; set; } /// /// Gets a value indicating whether the content item is a blueprint. /// - bool Blueprint { get; } + bool Blueprint { get; set; } /// /// Gets the template id used to render the published version of the content. /// /// When editing the content, the template can change, but this will not until the content is published. - int? PublishTemplateId { get; } + int? PublishTemplateId { get; set; } /// /// Gets the name of the published version of the content. /// /// When editing the content, the name can change, but this will not until the content is published. - string PublishName { get; } + string PublishName { get; set; } /// /// Gets the identifier of the user who published the content. /// - int? PublisherId { get; } + int? PublisherId { get; set; } /// /// Gets the date and time the content was published. /// - DateTime? PublishDate { get; } - - /// - /// Gets the content type of this content. - /// - IContentType ContentType { get; } + DateTime? PublishDate { get; set; } /// /// Gets a value indicating whether a culture is published. @@ -79,19 +74,11 @@ namespace Umbraco.Core.Models /// and the content published name for this culture is non-null. It becomes non-published /// whenever values for this culture are unpublished. /// A culture becomes published as soon as PublishCulture has been invoked, - /// even though the document might now have been saved yet (and can have no identity). + /// even though the document might not have been saved yet (and can have no identity). /// Does not support the '*' wildcard (returns false). /// bool IsCulturePublished(string culture); - /// - /// Gets a value indicating whether a culture was published. - /// - /// - /// Mirrors whenever the content item is saved. - /// - bool WasCulturePublished(string culture); - /// /// Gets the date a culture was published. /// @@ -125,7 +112,7 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot get the invariant /// name, which must be get via the property. /// - IReadOnlyDictionary PublishCultureInfos { get; } + ContentCultureInfosCollection PublishCultureInfos { get; set; } /// /// Gets the published cultures. @@ -135,47 +122,13 @@ namespace Umbraco.Core.Models /// /// Gets the edited cultures. /// - IEnumerable EditedCultures { get; } - - // TODO: these two should move to some kind of service - - /// - /// Changes the for the current content object - /// - /// New ContentType for this content - /// Leaves PropertyTypes intact after change - void ChangeContentType(IContentType contentType); - - /// - /// Changes the for the current content object and removes PropertyTypes, - /// which are not part of the new ContentType. - /// - /// New ContentType for this content - /// Boolean indicating whether to clear PropertyTypes upon change - void ChangeContentType(IContentType contentType, bool clearProperties); + IEnumerable EditedCultures { get; set; } /// /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset /// /// IContent DeepCloneWithResetIdentities(); - - /// - /// Registers a culture to be published. - /// - /// A value indicating whether the culture can be published. - /// - /// Fails if properties don't pass variant validation rules. - /// Publishing must be finalized via the content service SavePublishing method. - /// - bool PublishCulture(string culture = "*"); - - /// - /// Registers a culture to be unpublished. - /// - /// - /// Unpublishing must be finalized via the content service SavePublishing method. - /// - void UnpublishCulture(string culture = "*"); + } } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 40a1c57097..0f660181fb 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -18,6 +18,11 @@ namespace Umbraco.Core.Models /// int ContentTypeId { get; } + /// + /// Gets the content type of this content. + /// + ISimpleContentType ContentType { get; } + /// /// Gets the identifier of the writer. /// @@ -26,7 +31,7 @@ namespace Umbraco.Core.Models /// /// Gets the version identifier. /// - int VersionId { get; } + int VersionId { get; set; } /// /// Sets the name of the content item for a specified culture. @@ -57,7 +62,7 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot contain the invariant /// culture name, which must be get or set via the property. /// - IReadOnlyDictionary CultureInfos { get; } + ContentCultureInfosCollection CultureInfos { get; set; } /// /// Gets the available cultures. @@ -95,18 +100,6 @@ namespace Umbraco.Core.Models /// Properties are loaded as part of the Content object graph PropertyCollection Properties { get; set; } - /// - /// List of PropertyGroups available on this Content object - /// - /// PropertyGroups are kind of lazy loaded as part of the object graph - IEnumerable PropertyGroups { get; } - - /// - /// List of PropertyTypes available on this Content object - /// - /// PropertyTypes are kind of lazy loaded as part of the object graph - IEnumerable PropertyTypes { get; } - /// /// Gets a value indicating whether the content entity has a property with the supplied alias. /// @@ -132,16 +125,5 @@ namespace Umbraco.Core.Models /// Values 'null' and 'empty' are equivalent for culture and segment. void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null); - /// - /// Copies values from another document. - /// - void CopyFrom(IContent other, string culture = "*"); - - /// - /// Validates the content item's properties pass variant rules - /// - /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor - /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. - Property[] ValidateProperties(string culture = "*"); } } diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index aea84d9767..215c8532c1 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -159,5 +159,10 @@ namespace Umbraco.Core.Models /// Name of the PropertyGroup to move the PropertyType to /// bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); + + /// + /// Gets an corresponding to this content type. + /// + ISimpleContentType ToSimple(); } } diff --git a/src/Umbraco.Core/Models/IMedia.cs b/src/Umbraco.Core/Models/IMedia.cs index cfb79bbf2c..75e94d66e7 100644 --- a/src/Umbraco.Core/Models/IMedia.cs +++ b/src/Umbraco.Core/Models/IMedia.cs @@ -1,27 +1,5 @@ -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models +namespace Umbraco.Core.Models { public interface IMedia : IContentBase - { - /// - /// Gets the ContentType used by this Media object - /// - IMediaType ContentType { get; } - - /// - /// Changes the for the current content object - /// - /// New ContentType for this content - /// Leaves PropertyTypes intact after change - void ChangeContentType(IMediaType contentType); - - /// - /// Changes the for the current content object and removes PropertyTypes, - /// which are not part of the new ContentType. - /// - /// New ContentType for this content - /// Boolean indicating whether to clear PropertyTypes upon change - void ChangeContentType(IMediaType contentType, bool clearProperties); - } + { } } diff --git a/src/Umbraco.Core/Models/IMember.cs b/src/Umbraco.Core/Models/IMember.cs index 15826f03f3..199d53b174 100644 --- a/src/Umbraco.Core/Models/IMember.cs +++ b/src/Umbraco.Core/Models/IMember.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Models @@ -11,10 +9,5 @@ namespace Umbraco.Core.Models /// String alias of the default ContentType /// string ContentTypeAlias { get; } - - /// - /// Gets the ContentType used by this content object - /// - IMemberType ContentType { get; } } } diff --git a/src/Umbraco.Core/Models/ISimpleContentType.cs b/src/Umbraco.Core/Models/ISimpleContentType.cs new file mode 100644 index 0000000000..740db8e73f --- /dev/null +++ b/src/Umbraco.Core/Models/ISimpleContentType.cs @@ -0,0 +1,62 @@ +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a simplified view of a content type. + /// + public interface ISimpleContentType : IUmbracoEntity + { + new int Id { get; } + new string Name { get; } + + /// + /// Gets the alias of the content type. + /// + string Alias { get; } + + /// + /// Gets the default template of the content type. + /// + ITemplate DefaultTemplate { get; } + + /// + /// Gets the content variation of the content type. + /// + ContentVariation Variations { get; } + + /// + /// Gets the icon of the content type. + /// + string Icon { get; } + + /// + /// Gets a value indicating whether the content type is a container. + /// + bool IsContainer { get; } + + /// + /// Gets a value indicating whether content of that type can be created at the root of the tree. + /// + bool AllowedAsRoot { get; } + + /// + /// Gets a value indicating whether the content type is an element content type. + /// + bool IsElement { get; } + + /// + /// Validates that a combination of culture and segment is valid for the content type properties. + /// + /// The culture. + /// The segment. + /// A value indicating whether wildcard are supported. + /// True if the combination is valid; otherwise false. + /// + /// The combination must be valid for properties of the content type. For instance, if the content type varies by culture, + /// then an invariant culture is valid, because some properties may be invariant. On the other hand, if the content type is invariant, + /// then a variant culture is invalid, because no property could possibly vary by culture. + /// + bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false); + } +} diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index f5c21a3a74..0eb53428d1 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -3,21 +3,14 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; -using System.Reflection; -using System.Security.Claims; -using System.Threading.Tasks; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; namespace Umbraco.Core.Models.Identity { public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim>, IRememberBeingDirty { - private static readonly Lazy Ps = new Lazy(); - private string _email; private string _userName; private int _id; @@ -118,7 +111,7 @@ namespace Umbraco.Core.Models.Identity public override string Email { get => _email; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); } /// @@ -127,7 +120,7 @@ namespace Umbraco.Core.Models.Identity public override string UserName { get => _userName; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, Ps.Value.UserNameSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, nameof(UserName)); } /// @@ -136,7 +129,7 @@ namespace Umbraco.Core.Models.Identity public override DateTime? LastPasswordChangeDateUtc { get { return _lastPasswordChangeDateUtc; } - set { _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, Ps.Value.LastPasswordChangeDateUtcSelector); } + set { _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, nameof(LastPasswordChangeDateUtc)); } } /// @@ -145,7 +138,7 @@ namespace Umbraco.Core.Models.Identity public override DateTime? LastLoginDateUtc { get => _lastLoginDateUtc; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, Ps.Value.LastLoginDateUtcSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, nameof(LastLoginDateUtc)); } /// @@ -154,7 +147,7 @@ namespace Umbraco.Core.Models.Identity public override bool EmailConfirmed { get => _emailConfirmed; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, Ps.Value.EmailConfirmedSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, nameof(EmailConfirmed)); } /// @@ -163,7 +156,7 @@ namespace Umbraco.Core.Models.Identity public string Name { get => _name; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -172,7 +165,7 @@ namespace Umbraco.Core.Models.Identity public override int AccessFailedCount { get => _accessFailedCount; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, Ps.Value.AccessFailedCountSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, nameof(AccessFailedCount)); } /// @@ -181,7 +174,7 @@ namespace Umbraco.Core.Models.Identity public override string PasswordHash { get => _passwordHash; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, Ps.Value.PasswordHashSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, nameof(PasswordHash)); } @@ -194,7 +187,7 @@ namespace Umbraco.Core.Models.Identity set { if (value == null) value = new int[0]; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdsSelector, Ps.Value.StartIdsComparer); + _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), StartIdsComparer); } } @@ -207,7 +200,7 @@ namespace Umbraco.Core.Models.Identity set { if (value == null) value = new int[0]; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdsSelector, Ps.Value.StartIdsComparer); + _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), StartIdsComparer); } } @@ -222,7 +215,7 @@ namespace Umbraco.Core.Models.Identity public string Culture { get => _culture; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); } public IReadOnlyUserGroup[] Groups @@ -246,7 +239,7 @@ namespace Umbraco.Core.Models.Identity } _roles.CollectionChanged += _roles_CollectionChanged; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, Ps.Value.GroupsSelector, Ps.Value.GroupsComparer); + _beingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), GroupsComparer); } } @@ -302,12 +295,12 @@ namespace Umbraco.Core.Models.Identity void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - _beingDirty.OnPropertyChanged(Ps.Value.LoginsSelector); + _beingDirty.OnPropertyChanged(nameof(Logins)); } private void _roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - _beingDirty.OnPropertyChanged(Ps.Value.RolesSelector); + _beingDirty.OnPropertyChanged(nameof(Roles)); } private readonly ObservableCollection> _roles; @@ -416,33 +409,13 @@ namespace Umbraco.Core.Models.Identity #endregion - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - public readonly PropertyInfo UserNameSelector = ExpressionHelper.GetPropertyInfo(x => x.UserName); - public readonly PropertyInfo LastLoginDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDateUtc); - public readonly PropertyInfo LastPasswordChangeDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDateUtc); - public readonly PropertyInfo EmailConfirmedSelector = ExpressionHelper.GetPropertyInfo(x => x.EmailConfirmed); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AccessFailedCountSelector = ExpressionHelper.GetPropertyInfo(x => x.AccessFailedCount); - public readonly PropertyInfo PasswordHashSelector = ExpressionHelper.GetPropertyInfo(x => x.PasswordHash); - public readonly PropertyInfo CultureSelector = ExpressionHelper.GetPropertyInfo(x => x.Culture); - public readonly PropertyInfo StartMediaIdsSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds); - public readonly PropertyInfo StartContentIdsSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds); - public readonly PropertyInfo GroupsSelector = ExpressionHelper.GetPropertyInfo(x => x.Groups); - public readonly PropertyInfo LoginsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Logins); - public readonly PropertyInfo RolesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.Roles); - - //Custom comparer for enumerables - public readonly DelegateEqualityComparer GroupsComparer = new DelegateEqualityComparer( - (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)), - groups => groups.GetHashCode()); - public readonly DelegateEqualityComparer StartIdsComparer = new DelegateEqualityComparer( - (groups, enumerable) => groups.UnsortedSequenceEqual(enumerable), - groups => groups.GetHashCode()); - - } + //Custom comparer for enumerables + private static readonly DelegateEqualityComparer GroupsComparer = new DelegateEqualityComparer( + (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)), + groups => groups.GetHashCode()); + private static readonly DelegateEqualityComparer StartIdsComparer = new DelegateEqualityComparer( + (groups, enumerable) => groups.UnsortedSequenceEqual(enumerable), + groups => groups.GetHashCode()); } } diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index 03f8f87cd3..7d5fda3e06 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -1,7 +1,5 @@ using System; using System.Globalization; -using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using System.Threading; using Umbraco.Core.Configuration; @@ -16,8 +14,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Language : EntityBase, ILanguage { - private static readonly Lazy Ps = new Lazy(); - private string _isoCode; private string _cultureName; private bool _isDefaultVariantLanguage; @@ -29,22 +25,12 @@ namespace Umbraco.Core.Models IsoCode = isoCode; } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); - public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); - public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefault); - public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.IsMandatory); - public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.FallbackLanguageId); - } - /// [DataMember] public string IsoCode { get => _isoCode; - set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); + set => SetPropertyValueAndDetectChanges(value, ref _isoCode, nameof(IsoCode)); } /// @@ -67,7 +53,7 @@ namespace Umbraco.Core.Models // culture // // I assume that, on a site, all language names should be in the SAME language, in DB, - // and that would be the umbracoDefaultUILanguage (app setting) - BUT if by accident + // and that would be the Umbraco.Core.DefaultUILanguage (app setting) - BUT if by accident // ANY culture has been retrieved with another current thread culture - it's now corrupt // // so, the logic below ensures that the name always end up being the correct name @@ -102,7 +88,7 @@ namespace Umbraco.Core.Models } } - set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); + set => SetPropertyValueAndDetectChanges(value, ref _cultureName, nameof(CultureName)); } /// @@ -113,21 +99,21 @@ namespace Umbraco.Core.Models public bool IsDefault { get => _isDefaultVariantLanguage; - set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector); + set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, nameof(IsDefault)); } /// public bool IsMandatory { get => _mandatory; - set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(IsMandatory)); } /// public int? FallbackLanguageId { get => _fallbackLanguageId; - set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector); + set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, nameof(FallbackLanguageId)); } } } diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index 5ef49305ac..4f9e79f482 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; @@ -99,25 +98,9 @@ namespace Umbraco.Core.Models private List _addedProperties; private List _removedProperties; - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor); - public readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration); - public readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage); - public readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember); - public readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender); - public readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.MacroSource); - public readonly PropertyInfo MacroTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.MacroType); - public readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); - } - void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.PropertiesSelector); + OnPropertyChanged(nameof(Properties)); if (e.Action == NotifyCollectionChangedAction.Add) { @@ -155,35 +138,31 @@ namespace Umbraco.Core.Models /// void PropertyDataChanged(object sender, PropertyChangedEventArgs e) { - OnPropertyChanged(Ps.Value.PropertiesSelector); + OnPropertyChanged(nameof(Properties)); } public override void ResetDirtyProperties(bool rememberDirty) - { + { + base.ResetDirtyProperties(rememberDirty); + _addedProperties.Clear(); _removedProperties.Clear(); - base.ResetDirtyProperties(rememberDirty); + foreach (var prop in Properties) { - ((BeingDirtyBase)prop).ResetDirtyProperties(rememberDirty); + prop.ResetDirtyProperties(rememberDirty); } } /// /// Used internally to check if we need to add a section in the repository to the db /// - internal IEnumerable AddedProperties - { - get { return _addedProperties; } - } + internal IEnumerable AddedProperties => _addedProperties; /// /// Used internally to check if we need to remove a section in the repository to the db /// - internal IEnumerable RemovedProperties - { - get { return _removedProperties; } - } + internal IEnumerable RemovedProperties => _removedProperties; /// /// Gets or sets the alias of the Macro @@ -191,8 +170,8 @@ namespace Umbraco.Core.Models [DataMember] public string Alias { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias), ref _alias, Ps.Value.AliasSelector); } + get => _alias; + set => SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias), ref _alias, nameof(Alias)); } /// @@ -201,8 +180,8 @@ namespace Umbraco.Core.Models [DataMember] public string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -211,8 +190,8 @@ namespace Umbraco.Core.Models [DataMember] public bool UseInEditor { - get { return _useInEditor; } - set { SetPropertyValueAndDetectChanges(value, ref _useInEditor, Ps.Value.UseInEditorSelector); } + get => _useInEditor; + set => SetPropertyValueAndDetectChanges(value, ref _useInEditor, nameof(UseInEditor)); } /// @@ -221,8 +200,8 @@ namespace Umbraco.Core.Models [DataMember] public int CacheDuration { - get { return _cacheDuration; } - set { SetPropertyValueAndDetectChanges(value, ref _cacheDuration, Ps.Value.CacheDurationSelector); } + get => _cacheDuration; + set => SetPropertyValueAndDetectChanges(value, ref _cacheDuration, nameof(CacheDuration)); } /// @@ -231,8 +210,8 @@ namespace Umbraco.Core.Models [DataMember] public bool CacheByPage { - get { return _cacheByPage; } - set { SetPropertyValueAndDetectChanges(value, ref _cacheByPage, Ps.Value.CacheByPageSelector); } + get => _cacheByPage; + set => SetPropertyValueAndDetectChanges(value, ref _cacheByPage, nameof(CacheByPage)); } /// @@ -241,8 +220,8 @@ namespace Umbraco.Core.Models [DataMember] public bool CacheByMember { - get { return _cacheByMember; } - set { SetPropertyValueAndDetectChanges(value, ref _cacheByMember, Ps.Value.CacheByMemberSelector); } + get => _cacheByMember; + set => SetPropertyValueAndDetectChanges(value, ref _cacheByMember, nameof(CacheByMember)); } /// @@ -251,8 +230,8 @@ namespace Umbraco.Core.Models [DataMember] public bool DontRender { - get { return _dontRender; } - set { SetPropertyValueAndDetectChanges(value, ref _dontRender, Ps.Value.DontRenderSelector); } + get => _dontRender; + set => SetPropertyValueAndDetectChanges(value, ref _dontRender, nameof(DontRender)); } /// @@ -261,8 +240,8 @@ namespace Umbraco.Core.Models [DataMember] public string MacroSource { - get { return _macroSource; } - set { SetPropertyValueAndDetectChanges(value, ref _macroSource, Ps.Value.ScriptPathSelector); } + get => _macroSource; + set => SetPropertyValueAndDetectChanges(value, ref _macroSource, nameof(MacroSource)); } /// @@ -271,18 +250,15 @@ namespace Umbraco.Core.Models [DataMember] public MacroTypes MacroType { - get { return _macroType; } - set { SetPropertyValueAndDetectChanges(value, ref _macroType, Ps.Value.MacroTypeSelector); } + get => _macroType; + set => SetPropertyValueAndDetectChanges(value, ref _macroType, nameof(MacroType)); } /// /// Gets or sets a list of Macro Properties /// [DataMember] - public MacroPropertyCollection Properties - { - get { return _properties; } - } + public MacroPropertyCollection Properties => _properties; protected override void PerformDeepClone(object clone) { diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index 380705b3d5..62ba6a7a7d 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -1,8 +1,6 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; -using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models { @@ -59,27 +57,15 @@ namespace Umbraco.Core.Models private int _sortOrder; private int _id; private string _editorAlias; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); - } - + /// /// Gets or sets the Key of the Property /// [DataMember] public Guid Key { - get { return _key; } - set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); } + get => _key; + set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key)); } /// @@ -88,8 +74,8 @@ namespace Umbraco.Core.Models [DataMember] public int Id { - get { return _id; } - set { SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); } + get => _id; + set => SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id)); } /// @@ -98,8 +84,8 @@ namespace Umbraco.Core.Models [DataMember] public string Alias { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } + get => _alias; + set => SetPropertyValueAndDetectChanges(value, ref _alias, nameof(Alias)); } /// @@ -108,8 +94,8 @@ namespace Umbraco.Core.Models [DataMember] public string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -118,8 +104,8 @@ namespace Umbraco.Core.Models [DataMember] public int SortOrder { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); } /// @@ -132,11 +118,8 @@ namespace Umbraco.Core.Models [DataMember] public string EditorAlias { - get { return _editorAlias; } - set - { - SetPropertyValueAndDetectChanges(value, ref _editorAlias, Ps.Value.PropertyTypeSelector); - } + get => _editorAlias; + set => SetPropertyValueAndDetectChanges(value, ref _editorAlias, nameof(EditorAlias)); } public object DeepClone() diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index a0f3aa35de..002611c09c 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -10,8 +10,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Media : ContentBase, IMedia { - private IMediaType _contentType; - /// /// Constructor for creating a Media object /// @@ -31,9 +29,7 @@ namespace Umbraco.Core.Models /// Collection of properties public Media(string name, IMedia parent, IMediaType contentType, PropertyCollection properties) : base(name, parent, contentType, properties) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } + { } /// /// Constructor for creating a Media object @@ -54,30 +50,16 @@ namespace Umbraco.Core.Models /// Collection of properties public Media(string name, int parentId, IMediaType contentType, PropertyCollection properties) : base(name, parentId, contentType, properties) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } - - /// - /// Gets the ContentType used by this Media object - /// - [IgnoreDataMember] - public IMediaType ContentType => _contentType; + { } /// /// Changes the for the current Media object /// /// New MediaType for this Media /// Leaves PropertyTypes intact after change - public void ChangeContentType(IMediaType contentType) + internal void ChangeContentType(IMediaType contentType) { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsurePropertyTypes(PropertyTypes); - - Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add - Properties.CollectionChanged += PropertiesChanged; + ChangeContentType(contentType, false); } /// @@ -86,33 +68,17 @@ namespace Umbraco.Core.Models /// /// New MediaType for this Media /// Boolean indicating whether to clear PropertyTypes upon change - public void ChangeContentType(IMediaType contentType, bool clearProperties) + internal void ChangeContentType(IMediaType contentType, bool clearProperties) { + ChangeContentType(new SimpleContentType(contentType)); + if (clearProperties) - { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsureCleanPropertyTypes(PropertyTypes); + Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes); + else + Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); - Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add - Properties.CollectionChanged += PropertiesChanged; - return; - } - - ChangeContentType(contentType); - } - - /// - /// Changes the Trashed state of the content object - /// - /// Boolean indicating whether content is trashed (true) or not trashed (false) - /// - public void ChangeTrashedState(bool isTrashed, int parentId = -20) - { - Trashed = isTrashed; - //The Media Recycle Bin Id is -21 so we correct that here - ParentId = parentId == -20 ? -21 : parentId; + Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add + Properties.CollectionChanged += PropertiesChanged; } } } diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index e281aaf5ab..1166698adb 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -15,20 +15,17 @@ namespace Umbraco.Core.Models /// public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger) { - var propertyType = media.PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyAlias)); - if (propertyType == null) return string.Empty; - - var val = media.Properties[propertyType]; - if (val == null) return string.Empty; + if (!media.Properties.TryGetValue(propertyAlias, out var property)) + return string.Empty; // TODO: would need to be adjusted to variations, when media become variants - var jsonString = val.GetValue() as string; - if (jsonString == null) return string.Empty; + if (!(property.GetValue() is string jsonString)) + return string.Empty; - if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField) + if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField) return jsonString; - if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper) + if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper) { if (jsonString.DetectIsJson() == false) return jsonString; diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index 83e1acfbc0..2f087979f7 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class MediaType : ContentTypeCompositionBase, IMediaType { - public const bool IsPublishingConst = false; + public const bool SupportsPublishingConst = false; /// /// Constuctor for creating a MediaType with the parent's id. @@ -42,7 +42,10 @@ namespace Umbraco.Core.Models } /// - public override bool IsPublishing => IsPublishingConst; + public override ISimpleContentType ToSimple() => new SimpleContentType(this); + + /// + public override bool SupportsPublishing => SupportsPublishingConst; /// IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) => (IMediaType)DeepCloneWithResetIdentities(newAlias); diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 0ef15ee413..0e91065d56 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; -using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -19,8 +15,6 @@ namespace Umbraco.Core.Models public class Member : ContentBase, IMember { private IDictionary _additionalData; - private IMemberType _contentType; - private readonly string _contentTypeAlias; private string _username; private string _email; private string _rawPasswordValue; @@ -33,8 +27,6 @@ namespace Umbraco.Core.Models public Member(IMemberType contentType) : base("", -1, contentType, new PropertyCollection()) { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; IsApproved = true; //this cannot be null but can be empty @@ -53,8 +45,6 @@ namespace Umbraco.Core.Models { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; IsApproved = true; //this cannot be null but can be empty @@ -77,8 +67,6 @@ namespace Umbraco.Core.Models if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullOrEmptyException(nameof(username)); - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; _email = email; _username = username; IsApproved = isApproved; @@ -100,9 +88,6 @@ namespace Umbraco.Core.Models public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType) : base(name, -1, contentType, new PropertyCollection()) { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; - _email = email; _username = username; _rawPasswordValue = rawPasswordValue; @@ -123,32 +108,20 @@ namespace Umbraco.Core.Models public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved) : base(name, -1, contentType, new PropertyCollection()) { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; _email = email; _username = username; _rawPasswordValue = rawPasswordValue; IsApproved = isApproved; } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - public readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); - } - /// /// Gets or sets the Username /// [DataMember] public string Username { - get { return _username; } - set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } + get => _username; + set => SetPropertyValueAndDetectChanges(value, ref _username, nameof(Username)); } /// @@ -157,8 +130,8 @@ namespace Umbraco.Core.Models [DataMember] public string Email { - get { return _email; } - set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } + get => _email; + set => SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); } /// @@ -167,7 +140,7 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public string RawPasswordValue { - get { return _rawPasswordValue; } + get => _rawPasswordValue; set { if (value == null) @@ -178,7 +151,7 @@ namespace Umbraco.Core.Models } else { - SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); + SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, nameof(RawPasswordValue)); } } } @@ -487,10 +460,7 @@ namespace Umbraco.Core.Models /// String alias of the default ContentType /// [DataMember] - public virtual string ContentTypeAlias - { - get { return _contentTypeAlias; } - } + public virtual string ContentTypeAlias => ContentType.Alias; /// /// User key from the Provider. @@ -504,11 +474,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual object ProviderUserKey { - get - { - return _providerUserKey; - } - set { SetPropertyValueAndDetectChanges(value, ref _providerUserKey, Ps.Value.ProviderUserKeySelector); } + get => _providerUserKey; + set => SetPropertyValueAndDetectChanges(value, ref _providerUserKey, nameof(ProviderUserKey)); } @@ -524,15 +491,6 @@ namespace Umbraco.Core.Models ProviderUserKey = Key; } - /// - /// Gets the ContentType used by this content object - /// - [IgnoreDataMember] - public IMemberType ContentType - { - get { return _contentType; } - } - /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ @@ -598,17 +556,6 @@ namespace Umbraco.Core.Models return true; } - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); - - var clonedEntity = (Member)clone; - - //need to manually clone this since it's not settable - clonedEntity._contentType = (IMemberType)ContentType.DeepClone(); - - } - /// [DataMember] [DoNotClone] diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index 0653f75ef7..8c06da418d 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -13,19 +12,10 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class MemberGroup : EntityBase, IMemberGroup { - private static PropertySelectors _selectors; private IDictionary _additionalData; private string _name; private int _creatorId; - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - - private class PropertySelectors - { - public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo CreatorId = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - } - /// [DataMember] [DoNotClone] @@ -49,7 +39,7 @@ namespace Umbraco.Core.Models AdditionalData["previousName"] = _name; } - SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); + SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } } @@ -57,7 +47,7 @@ namespace Umbraco.Core.Models public int CreatorId { get => _creatorId; - set => SetPropertyValueAndDetectChanges(value, ref _creatorId, Selectors.CreatorId); + set => SetPropertyValueAndDetectChanges(value, ref _creatorId, nameof(CreatorId)); } } } diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 1ce883d9a7..4633775a43 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models @@ -12,8 +11,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class MemberType : ContentTypeCompositionBase, IMemberType { - private static readonly Lazy Ps = new Lazy(); - public const bool IsPublishingConst = false; + public const bool SupportsPublishingConst = false; //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId private string _alias; @@ -34,7 +32,10 @@ namespace Umbraco.Core.Models } /// - public override bool IsPublishing => IsPublishingConst; + public override ISimpleContentType ToSimple() => new SimpleContentType(this); + + /// + public override bool SupportsPublishing => SupportsPublishingConst; public override ContentVariation Variations { @@ -46,12 +47,6 @@ namespace Umbraco.Core.Models set => throw new NotSupportedException("Variations are not supported on members."); } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - } - /// /// The Alias of the ContentType /// @@ -74,7 +69,7 @@ namespace Umbraco.Core.Models ? value : (value == null ? string.Empty : value.ToSafeAlias()); - SetPropertyValueAndDetectChanges(newVal, ref _alias, Ps.Value.AliasSelector); + SetPropertyValueAndDetectChanges(newVal, ref _alias, nameof(Alias)); } } diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 7832390b92..2fb293c349 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -1,14 +1,8 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Membership @@ -123,123 +117,92 @@ namespace Umbraco.Core.Models.Membership private IDictionary _additionalData; private object _additionalDataLock = new object(); - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo(x => x.FailedPasswordAttempts); - public readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLockoutDate); - public readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDate); - public readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); - - public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); - public readonly PropertyInfo AvatarSelector = ExpressionHelper.GetPropertyInfo(x => x.Avatar); - public readonly PropertyInfo TourDataSelector = ExpressionHelper.GetPropertyInfo(x => x.TourData); - public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); - public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds); - public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - - public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - public readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo(x => x.IsLockedOut); - public readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); - public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - public readonly PropertyInfo EmailConfirmedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.EmailConfirmedDate); - public readonly PropertyInfo InvitedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.InvitedDate); - - public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); - - public readonly PropertyInfo UserGroupsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Groups); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> IntegerEnumerableComparer = - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode()); - } + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> IntegerEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); #region Implementation of IMembershipUser [IgnoreDataMember] public object ProviderUserKey { - get { return Id; } - set { throw new NotSupportedException("Cannot set the provider user key for a user"); } + get => Id; + set => throw new NotSupportedException("Cannot set the provider user key for a user"); } [DataMember] public DateTime? EmailConfirmedDate { - get { return _emailConfirmedDate; } - set { SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, Ps.Value.EmailConfirmedDateSelector); } + get => _emailConfirmedDate; + set => SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, nameof(EmailConfirmedDate)); } [DataMember] public DateTime? InvitedDate { - get { return _invitedDate; } - set { SetPropertyValueAndDetectChanges(value, ref _invitedDate, Ps.Value.InvitedDateSelector); } + get => _invitedDate; + set => SetPropertyValueAndDetectChanges(value, ref _invitedDate, nameof(InvitedDate)); } [DataMember] public string Username { - get { return _username; } - set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } + get => _username; + set => SetPropertyValueAndDetectChanges(value, ref _username, nameof(Username)); } [DataMember] public string Email { - get { return _email; } - set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } + get => _email; + set => SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); } [DataMember] public string RawPasswordValue { - get { return _rawPasswordValue; } - set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } + get => _rawPasswordValue; + set => SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, nameof(RawPasswordValue)); } [DataMember] public bool IsApproved { - get { return _isApproved; } - set { SetPropertyValueAndDetectChanges(value, ref _isApproved, Ps.Value.IsApprovedSelector); } + get => _isApproved; + set => SetPropertyValueAndDetectChanges(value, ref _isApproved, nameof(IsApproved)); } [IgnoreDataMember] public bool IsLockedOut { - get { return _isLockedOut; } - set { SetPropertyValueAndDetectChanges(value, ref _isLockedOut, Ps.Value.IsLockedOutSelector); } + get => _isLockedOut; + set => SetPropertyValueAndDetectChanges(value, ref _isLockedOut, nameof(IsLockedOut)); } [IgnoreDataMember] public DateTime LastLoginDate { - get { return _lastLoginDate; } - set { SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, Ps.Value.LastLoginDateSelector); } + get => _lastLoginDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, nameof(LastLoginDate)); } [IgnoreDataMember] public DateTime LastPasswordChangeDate { - get { return _lastPasswordChangedDate; } - set { SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, Ps.Value.LastPasswordChangeDateSelector); } + get => _lastPasswordChangedDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, nameof(LastPasswordChangeDate)); } [IgnoreDataMember] public DateTime LastLockoutDate { - get { return _lastLockoutDate; } - set { SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, Ps.Value.LastLockoutDateSelector); } + get => _lastLockoutDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, nameof(LastLockoutDate)); } [IgnoreDataMember] public int FailedPasswordAttempts { - get { return _failedLoginAttempts; } - set { SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, Ps.Value.FailedPasswordAttemptsSelector); } + get => _failedLoginAttempts; + set => SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, nameof(FailedPasswordAttempts)); } // TODO: Figure out how to support all of this! - we cannot have NotImplementedExceptions because these get used by the IMembershipMemberService service so @@ -279,8 +242,8 @@ namespace Umbraco.Core.Models.Membership [DataMember] public string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } public IEnumerable AllowedSections @@ -295,10 +258,7 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] internal List GroupsToSave = new List(); - public IProfile ProfileData - { - get { return new WrappedUserProfile(this); } - } + public IProfile ProfileData => new WrappedUserProfile(this); /// /// The security stamp used by ASP.Net identity @@ -306,15 +266,15 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public string SecurityStamp { - get { return _securityStamp; } - set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); } + get => _securityStamp; + set => SetPropertyValueAndDetectChanges(value, ref _securityStamp, nameof(SecurityStamp)); } [DataMember] public string Avatar { - get { return _avatar; } - set { SetPropertyValueAndDetectChanges(value, ref _avatar, Ps.Value.AvatarSelector); } + get => _avatar; + set => SetPropertyValueAndDetectChanges(value, ref _avatar, nameof(Avatar)); } /// @@ -323,8 +283,8 @@ namespace Umbraco.Core.Models.Membership [DataMember] public string TourData { - get { return _tourData; } - set { SetPropertyValueAndDetectChanges(value, ref _tourData, Ps.Value.TourDataSelector); } + get => _tourData; + set => SetPropertyValueAndDetectChanges(value, ref _tourData, nameof(TourData)); } /// @@ -336,8 +296,8 @@ namespace Umbraco.Core.Models.Membership [DataMember] public int SessionTimeout { - get { return _sessionTimeout; } - set { SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, Ps.Value.SessionTimeoutSelector); } + get => _sessionTimeout; + set => SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, nameof(SessionTimeout)); } /// @@ -350,8 +310,8 @@ namespace Umbraco.Core.Models.Membership [DoNotClone] public int[] StartContentIds { - get { return _startContentIds; } - set { SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdSelector, Ps.Value.IntegerEnumerableComparer); } + get => _startContentIds; + set => SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), IntegerEnumerableComparer); } /// @@ -364,32 +324,29 @@ namespace Umbraco.Core.Models.Membership [DoNotClone] public int[] StartMediaIds { - get { return _startMediaIds; } - set { SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdSelector, Ps.Value.IntegerEnumerableComparer); } + get => _startMediaIds; + set => SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), IntegerEnumerableComparer); } [DataMember] public string Language { - get { return _language; } - set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } + get => _language; + set => SetPropertyValueAndDetectChanges(value, ref _language, nameof(Language)); } [IgnoreDataMember] internal bool DefaultToLiveEditing { - get { return _defaultToLiveEditing; } - set { SetPropertyValueAndDetectChanges(value, ref _defaultToLiveEditing, Ps.Value.DefaultToLiveEditingSelector); } + get => _defaultToLiveEditing; + set => SetPropertyValueAndDetectChanges(value, ref _defaultToLiveEditing, nameof(DefaultToLiveEditing)); } /// /// Gets the groups that user is part of /// [DataMember] - public IEnumerable Groups - { - get { return _userGroups; } - } + public IEnumerable Groups => _userGroups; public void RemoveGroup(string group) { @@ -400,7 +357,7 @@ namespace Umbraco.Core.Models.Membership _userGroups.Remove(userGroup); //reset this flag so it's rebuilt with the assigned groups _allowedSections = null; - OnPropertyChanged(Ps.Value.UserGroupsSelector); + OnPropertyChanged(nameof(Groups)); } } } @@ -412,7 +369,7 @@ namespace Umbraco.Core.Models.Membership _userGroups.Clear(); //reset this flag so it's rebuilt with the assigned groups _allowedSections = null; - OnPropertyChanged(Ps.Value.UserGroupsSelector); + OnPropertyChanged(nameof(Groups)); } } @@ -422,7 +379,7 @@ namespace Umbraco.Core.Models.Membership { //reset this flag so it's rebuilt with the assigned groups _allowedSections = null; - OnPropertyChanged(Ps.Value.UserGroupsSelector); + OnPropertyChanged(nameof(Groups)); } } @@ -446,7 +403,7 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] [DoNotClone] - internal object AdditionalDataLock { get { return _additionalDataLock; } } + internal object AdditionalDataLock => _additionalDataLock; protected override void PerformDeepClone(object clone) { @@ -498,15 +455,9 @@ namespace Umbraco.Core.Models.Membership _user = user; } - public int Id - { - get { return _user.Id; } - } + public int Id => _user.Id; - public string Name - { - get { return _user.Name; } - } + public string Name => _user.Name; private bool Equals(WrappedUserProfile other) { diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index e3e812f4c1..31421f990d 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; @@ -22,24 +21,11 @@ namespace Umbraco.Core.Models.Membership private IEnumerable _permissions; private readonly List _sectionCollection; - private static readonly Lazy Ps = new Lazy(); - - // ReSharper disable once ClassNeverInstantiated.Local // lazy-instantiated in Ps - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); - public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); - public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); - public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> StringEnumerableComparer = - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode()); - } + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> StringEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); /// /// Constructor to create a new user group @@ -71,35 +57,35 @@ namespace Umbraco.Core.Models.Membership public int? StartMediaId { get => _startMediaId; - set => SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); + set => SetPropertyValueAndDetectChanges(value, ref _startMediaId, nameof(StartMediaId)); } [DataMember] public int? StartContentId { get => _startContentId; - set => SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); + set => SetPropertyValueAndDetectChanges(value, ref _startContentId, nameof(StartContentId)); } [DataMember] public string Icon { get => _icon; - set => SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); + set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon)); } [DataMember] public string Alias { get => _alias; - set => SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias, Ps.Value.AliasSelector); + set => SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias, nameof(Alias)); } [DataMember] public string Name { get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -112,7 +98,7 @@ namespace Umbraco.Core.Models.Membership public IEnumerable Permissions { get => _permissions; - set => SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, Ps.Value.StringEnumerableComparer); + set => SetPropertyValueAndDetectChanges(value, ref _permissions, nameof(Permissions), StringEnumerableComparer); } public IEnumerable AllowedSections => _sectionCollection; diff --git a/src/Umbraco.Core/Models/MigrationEntry.cs b/src/Umbraco.Core/Models/MigrationEntry.cs index 9ac9ae58a4..1af66d2978 100644 --- a/src/Umbraco.Core/Models/MigrationEntry.cs +++ b/src/Umbraco.Core/Models/MigrationEntry.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using Semver; using Umbraco.Core.Models.Entities; @@ -19,27 +18,19 @@ namespace Umbraco.Core.Models _version = version; } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.MigrationName); - public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); - } - private string _migrationName; private SemVersion _version; public string MigrationName { - get { return _migrationName; } - set { SetPropertyValueAndDetectChanges(value, ref _migrationName, Ps.Value.NameSelector); } + get => _migrationName; + set => SetPropertyValueAndDetectChanges(value, ref _migrationName, nameof(MigrationName)); } public SemVersion Version { - get { return _version; } - set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } + get => _version; + set => SetPropertyValueAndDetectChanges(value, ref _version, nameof(Version)); } } } diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs index f4ed11e09e..be514eaf44 100644 --- a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.Models.Packaging public RequirementsType UmbracoVersionRequirementsType { get; set; } public string Author { get; set; } public string AuthorUrl { get; set; } + public IList Contributors { get; set; } public string Readme { get; set; } public string PackageView { get; set; } public string IconUrl { get; set; } diff --git a/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs b/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs index eea56549f1..e4e9addf76 100644 --- a/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs +++ b/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Umbraco.Core.Models.Packaging { @@ -12,6 +13,7 @@ namespace Umbraco.Core.Models.Packaging Version UmbracoVersion { get; } string Author { get; } string AuthorUrl { get; } + IList Contributors { get; } string Readme { get; } /// diff --git a/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs b/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs index 8c0ef79d0b..070d0773d2 100644 --- a/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs +++ b/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core.Models.Packaging Actions = compiled.Actions, Author = compiled.Author, AuthorUrl = compiled.AuthorUrl, + Contributors = compiled.Contributors, PackageView = compiled.PackageView, IconUrl = compiled.IconUrl, License = compiled.License, @@ -79,6 +80,9 @@ namespace Umbraco.Core.Models.Packaging [Url] public string AuthorUrl { get; set; } = string.Empty; + [DataMember(Name = "contributors")] + public IList Contributors { get; set; } = new List(); + [DataMember(Name = "license")] public string License { get; set; } = "MIT License"; diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 11b5239a70..76349823ac 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Collections; using Umbraco.Core.Models.Entities; @@ -25,8 +24,6 @@ namespace Umbraco.Core.Models // _vvalues contains the (indexed) variant property values private Dictionary _vvalues; - private static readonly Lazy Ps = new Lazy(); - /// /// Initializes a new instance of the class. /// @@ -100,32 +97,25 @@ namespace Umbraco.Core.Models => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - // TODO: This allows us to track changes for an entire Property, but doesn't allow us to track changes at the variant level - public readonly PropertyInfo ValuesSelector = ExpressionHelper.GetPropertyInfo(x => x.Values); + private static readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( + (o, o1) => + { + if (o == null && o1 == null) return true; - public readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( - (o, o1) => - { - if (o == null && o1 == null) return true; + // custom comparer for strings. + // if one is null and another is empty then they are the same + if (o is string || o1 is string) + return ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) || (o != null && o1 != null && o.Equals(o1)); - // custom comparer for strings. - // if one is null and another is empty then they are the same - if (o is string || o1 is string) - return ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) || (o != null && o1 != null && o.Equals(o1)); + if (o == null || o1 == null) return false; - if (o == null || o1 == null) return false; + // custom comparer for enumerable + // ReSharper disable once MergeCastWithTypeCheck + if (o is IEnumerable && o1 is IEnumerable enumerable) + return ((IEnumerable)o).Cast().UnsortedSequenceEqual(enumerable.Cast()); - // custom comparer for enumerable - // ReSharper disable once MergeCastWithTypeCheck - if (o is IEnumerable && o1 is IEnumerable enumerable) - return ((IEnumerable) o).Cast().UnsortedSequenceEqual(enumerable.Cast()); - - return o.Equals(o1); - }, o => o.GetHashCode()); - } + return o.Equals(o1); + }, o => o.GetHashCode()); /// /// Returns the PropertyType, which this Property is based on @@ -194,7 +184,7 @@ namespace Umbraco.Core.Models { if (pvalue == null) return null; - return PropertyType.IsPublishing + return PropertyType.SupportsPublishing ? (published ? pvalue.PublishedValue : pvalue.EditedValue) : pvalue.EditedValue; } @@ -254,22 +244,22 @@ namespace Umbraco.Core.Models { if (pvalue == null) return; - if (!PropertyType.IsPublishing) + if (!PropertyType.SupportsPublishing) throw new NotSupportedException("Property type does not support publishing."); var origValue = pvalue.PublishedValue; pvalue.PublishedValue = PropertyType.ConvertAssignedValue(pvalue.EditedValue); - DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); + DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false); } private void UnpublishValue(PropertyValue pvalue) { if (pvalue == null) return; - if (!PropertyType.IsPublishing) + if (!PropertyType.SupportsPublishing) throw new NotSupportedException("Property type does not support publishing."); var origValue = pvalue.PublishedValue; pvalue.PublishedValue = PropertyType.ConvertAssignedValue(null); - DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); + DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false); } /// @@ -290,7 +280,7 @@ namespace Umbraco.Core.Models pvalue.EditedValue = setValue; - DetectChanges(setValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, change); + DetectChanges(setValue, origValue, nameof(Values), PropertyValueComparer, change); } // bypasses all changes detection and is the *only* way to set the published value @@ -298,7 +288,7 @@ namespace Umbraco.Core.Models { var (pvalue, _) = GetPValue(culture, segment, true); - if (published && PropertyType.IsPublishing) + if (published && PropertyType.SupportsPublishing) pvalue.PublishedValue = value; else pvalue.EditedValue = value; @@ -342,56 +332,6 @@ namespace Umbraco.Core.Models return (pvalue, change); } - /// - /// Gets a value indicating whether the property has valid values. - /// - internal bool IsValid(string culture = "*", string segment = "*") - { - // TODO: validating values shouldn't be done here, this calls in to IsValidValue - - culture = culture.NullOrWhiteSpaceAsNull(); - segment = segment.NullOrWhiteSpaceAsNull(); - - // if validating invariant/neutral, and it is supported, validate - // (including ensuring that the value exists, if mandatory) - if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null)) - if (!IsValidValue(_pvalue?.EditedValue)) - return false; - - // if validating only invariant/neutral, we are good - if (culture == null && segment == null) - return true; - - // if nothing else to validate, we are good - if ((culture == null || culture == "*") && (segment == null || segment == "*") && !PropertyType.VariesByCulture()) - return true; - - // for anything else, validate the existing values (including mandatory), - // but we cannot validate mandatory globally (we don't know the possible cultures and segments) - - if (_vvalues == null) return culture == "*" || IsValidValue(null); - - var pvalues = _vvalues.Where(x => - PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok - (culture == "*" || x.Value.Culture.InvariantEquals(culture)) && // the culture matches - (segment == "*" || x.Value.Segment.InvariantEquals(segment))) // the segment matches - .Select(x => x.Value) - .ToList(); - - return pvalues.Count == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); - } - - /// - /// Boolean indicating whether the passed in value is valid - /// - /// - /// True is property value is valid, otherwise false - private bool IsValidValue(object value) - { - // TODO: this shouldn't exist here, the model itself shouldn't be responsible for it's own validation and this requires singleton access - return PropertyType.IsPropertyValueValid(value); - } - protected override void PerformDeepClone(object clone) { base.PerformDeepClone(clone); diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 5e71fe9f65..977600a2f7 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -93,7 +93,7 @@ namespace Umbraco.Core.Models } /// - /// Adds a property. + /// Adds or updates a property. /// internal new void Add(Property property) { diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 595e8d1d6a..2e6da5d837 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Specialized; using System.Diagnostics; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -15,8 +14,6 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}")] public class PropertyGroup : EntityBase, IEquatable { - private static readonly Lazy Ps = new Lazy(); - private string _name; private int _sortOrder; private PropertyTypeCollection _propertyTypes; @@ -30,17 +27,9 @@ namespace Umbraco.Core.Models PropertyTypes = propertyTypeCollection; } - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo PropertyTypes = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); - } - private void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.PropertyTypes); + OnPropertyChanged(nameof(PropertyTypes)); } /// @@ -50,7 +39,7 @@ namespace Umbraco.Core.Models public string Name { get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -60,7 +49,7 @@ namespace Umbraco.Core.Models public int SortOrder { get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); } /// @@ -85,7 +74,7 @@ namespace Umbraco.Core.Models foreach (var propertyType in _propertyTypes) propertyType.PropertyGroupId = new Lazy(() => Id); - OnPropertyChanged(Ps.Value.PropertyTypes); + OnPropertyChanged(nameof(PropertyTypes)); _propertyTypes.CollectionChanged += PropertyTypesChanged; } } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 107831ebdd..1e950a876c 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; -using System.Text.RegularExpressions; using Umbraco.Core.Composing; using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; @@ -20,8 +16,6 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public class PropertyType : EntityBase, IEquatable { - private static PropertySelectors _selectors; - private readonly bool _forceValueStorageType; private string _name; private string _alias; @@ -87,27 +81,23 @@ namespace Umbraco.Core.Models _variations = ContentVariation.Nothing; } - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - - private class PropertySelectors - { - public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo Alias = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo Description = ExpressionHelper.GetPropertyInfo(x => x.Description); - public readonly PropertyInfo DataTypeId = ExpressionHelper.GetPropertyInfo(x => x.DataTypeId); - public readonly PropertyInfo PropertyEditorAlias = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - public readonly PropertyInfo ValueStorageType = ExpressionHelper.GetPropertyInfo(x => x.ValueStorageType); - public readonly PropertyInfo Mandatory = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); - public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo ValidationRegExp = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); - public readonly PropertyInfo PropertyGroupId = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); - public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); - } - /// - /// Gets a value indicating whether the content type, owning this property type, is publishing. + /// Gets a value indicating whether the content type owning this property type is publishing. /// - public bool IsPublishing { get; internal set; } + /// + /// A publishing content type supports draft and published values for properties. + /// It is possible to retrieve either the draft (default) or published value of a property. + /// Setting the value always sets the draft value, which then needs to be published. + /// A non-publishing content type only supports one value for properties. Getting + /// the draft or published value of a property returns the same thing, and publishing + /// a value property has no effect. + /// When true, getting the property value returns the edited value by default, but + /// it is possible to get the published value using the appropriate 'published' method + /// parameter. + /// When false, getting the property value always return the edited value, + /// regardless of the 'published' method parameter. + /// + public bool SupportsPublishing { get; internal set; } /// /// Gets of sets the name of the property type. @@ -116,7 +106,7 @@ namespace Umbraco.Core.Models public string Name { get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -126,7 +116,7 @@ namespace Umbraco.Core.Models public string Alias { get => _alias; - set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, Selectors.Alias); + set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, nameof(Alias)); } /// @@ -136,7 +126,7 @@ namespace Umbraco.Core.Models public string Description { get => _description; - set => SetPropertyValueAndDetectChanges(value, ref _description, Selectors.Description); + set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description)); } /// @@ -146,7 +136,7 @@ namespace Umbraco.Core.Models public int DataTypeId { get => _dataTypeId; - set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, Selectors.DataTypeId); + set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId)); } /// @@ -156,7 +146,7 @@ namespace Umbraco.Core.Models public string PropertyEditorAlias { get => _propertyEditorAlias; - set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Selectors.PropertyEditorAlias); + set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, nameof(PropertyEditorAlias)); } /// @@ -169,7 +159,7 @@ namespace Umbraco.Core.Models set { if (_forceValueStorageType) return; // ignore changes - SetPropertyValueAndDetectChanges(value, ref _valueStorageType, Selectors.ValueStorageType); + SetPropertyValueAndDetectChanges(value, ref _valueStorageType, nameof(ValueStorageType)); } } @@ -181,7 +171,7 @@ namespace Umbraco.Core.Models internal Lazy PropertyGroupId { get => _propertyGroupId; - set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Selectors.PropertyGroupId); + set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, nameof(PropertyGroupId)); } /// @@ -191,7 +181,7 @@ namespace Umbraco.Core.Models public bool Mandatory { get => _mandatory; - set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Selectors.Mandatory); + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(Mandatory)); } /// @@ -201,7 +191,7 @@ namespace Umbraco.Core.Models public int SortOrder { get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder); + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); } /// @@ -211,7 +201,7 @@ namespace Umbraco.Core.Models public string ValidationRegExp { get => _validationRegExp; - set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Selectors.ValidationRegExp); + set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, nameof(ValidationRegExp)); } /// @@ -220,7 +210,7 @@ namespace Umbraco.Core.Models public ContentVariation Variations { get => _variations; - set => SetPropertyValueAndDetectChanges(value, ref _variations, Selectors.VaryBy); + set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations)); } /// @@ -378,18 +368,6 @@ namespace Umbraco.Core.Models } - // TODO: this and other value validation methods should be a service level (not a model) thing. Changing this to internal for now - /// - /// Determines whether a value is valid for this property type. - /// - internal bool IsPropertyValueValid(object value) - { - var editor = Current.PropertyEditors[_propertyEditorAlias]; // TODO: inject - var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // TODO: inject - var valueEditor = editor.GetValueEditor(configuration); - return !valueEditor.Validate(value, Mandatory, ValidationRegExp).Any(); - } - /// /// Sanitizes a property type alias. /// diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index e79015d828..6181ee078b 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -23,18 +23,18 @@ namespace Umbraco.Core.Models [IgnoreDataMember] internal Action OnAdd; - internal PropertyTypeCollection(bool isPublishing) + internal PropertyTypeCollection(bool supportsPublishing) { - IsPublishing = isPublishing; + SupportsPublishing = supportsPublishing; } - public PropertyTypeCollection(bool isPublishing, IEnumerable properties) - : this(isPublishing) + public PropertyTypeCollection(bool supportsPublishing, IEnumerable properties) + : this(supportsPublishing) { Reset(properties); } - public bool IsPublishing { get; } + public bool SupportsPublishing { get; } /// /// Resets the collection to only contain the instances referenced in the parameter. @@ -51,7 +51,7 @@ namespace Umbraco.Core.Models protected override void SetItem(int index, PropertyType item) { - item.IsPublishing = IsPublishing; + item.SupportsPublishing = SupportsPublishing; base.SetItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } @@ -65,7 +65,7 @@ namespace Umbraco.Core.Models protected override void InsertItem(int index, PropertyType item) { - item.IsPublishing = IsPublishing; + item.SupportsPublishing = SupportsPublishing; base.InsertItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } @@ -79,7 +79,7 @@ namespace Umbraco.Core.Models // TODO: Instead of 'new' this should explicitly implement one of the collection interfaces members internal new void Add(PropertyType item) { - item.IsPublishing = IsPublishing; + item.SupportsPublishing = SupportsPublishing; // TODO: this is not pretty and should be refactored try @@ -155,7 +155,7 @@ namespace Umbraco.Core.Models public object DeepClone() { - var clone = new PropertyTypeCollection(IsPublishing); + var clone = new PropertyTypeCollection(SupportsPublishing); foreach (var propertyType in this) clone.Add((PropertyType) propertyType.DeepClone()); return clone; diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index df2d9f9ddc..cfb30de147 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -54,7 +53,7 @@ namespace Umbraco.Core.Models void _ruleCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(Ps.Value.AllowedSectionsSelector); + OnPropertyChanged(nameof(Rules)); //if (e.Action == NotifyCollectionChangedAction.Add) //{ @@ -77,26 +76,10 @@ namespace Umbraco.Core.Models } } + + internal IEnumerable RemovedRules => _removedRules; - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ProtectedNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ProtectedNodeId); - public readonly PropertyInfo LoginNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.LoginNodeId); - public readonly PropertyInfo NoAccessNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NoAccessNodeId); - public readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Rules); - } - - internal IEnumerable RemovedRules - { - get { return _removedRules; } - } - - public IEnumerable Rules - { - get { return _ruleCollection; } - } + public IEnumerable Rules => _ruleCollection; public PublicAccessRule AddRule(string ruleValue, string ruleType) { @@ -129,22 +112,22 @@ namespace Umbraco.Core.Models [DataMember] public int LoginNodeId { - get { return _loginNodeId; } - set { SetPropertyValueAndDetectChanges(value, ref _loginNodeId, Ps.Value.LoginNodeIdSelector); } + get => _loginNodeId; + set => SetPropertyValueAndDetectChanges(value, ref _loginNodeId, nameof(LoginNodeId)); } [DataMember] public int NoAccessNodeId { - get { return _noAccessNodeId; } - set { SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, Ps.Value.NoAccessNodeIdSelector); } + get => _noAccessNodeId; + set => SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, nameof(NoAccessNodeId)); } [DataMember] public int ProtectedNodeId { - get { return _protectedNodeId; } - set { SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, Ps.Value.ProtectedNodeIdSelector); } + get => _protectedNodeId; + set => SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, nameof(ProtectedNodeId)); } public override void ResetDirtyProperties(bool rememberDirty) diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs index 67b9ece2f9..bb6c1cdea2 100644 --- a/src/Umbraco.Core/Models/PublicAccessRule.cs +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -23,26 +22,18 @@ namespace Umbraco.Core.Models { } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo RuleValueSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleValue); - public readonly PropertyInfo RuleTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleType); - } - public Guid AccessEntryId { get; internal set; } public string RuleValue { - get { return _ruleValue; } - set { SetPropertyValueAndDetectChanges(value, ref _ruleValue, Ps.Value.RuleValueSelector); } + get => _ruleValue; + set => SetPropertyValueAndDetectChanges(value, ref _ruleValue, nameof(RuleValue)); } public string RuleType { - get { return _ruleType; } - set { SetPropertyValueAndDetectChanges(value, ref _ruleType, Ps.Value.RuleTypeSelector); } + get => _ruleType; + set => SetPropertyValueAndDetectChanges(value, ref _ruleType, nameof(RuleType)); } diff --git a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs new file mode 100644 index 0000000000..4027184f3c --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides a live published model creation service. + /// + public interface ILivePublishedModelFactory : IPublishedModelFactory + { + /// + /// Gets an object that can be used to synchronize access to the factory. + /// + object SyncRoot { get; } + + /// + /// Refreshes the factory. + /// + void Refresh(); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index abf4156d7f..2ca3593b55 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -70,9 +70,7 @@ namespace Umbraco.Core.Models.PublishedContent if (_publishedDataTypes == null) { var dataTypes = _dataTypeService.GetAll(); - _publishedDataTypes = dataTypes.ToDictionary( - x => x.Id, - x => new PublishedDataType(x.Id, x.EditorAlias, x is DataType d ? d.GetLazyConfiguration() : new Lazy(() => x.Configuration))); + _publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType); } publishedDataTypes = _publishedDataTypes; @@ -89,12 +87,24 @@ namespace Umbraco.Core.Models.PublishedContent { lock (_publishedDataTypesLocker) { - foreach (var id in ids) - _publishedDataTypes.Remove(id); - var dataTypes = _dataTypeService.GetAll(ids); - foreach (var dataType in dataTypes) - _publishedDataTypes[dataType.Id] = new PublishedDataType(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy(() => dataType.Configuration)); + if (_publishedDataTypes == null) + { + var dataTypes = _dataTypeService.GetAll(); + _publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType); + } + else + { + foreach (var id in ids) + _publishedDataTypes.Remove(id); + + var dataTypes = _dataTypeService.GetAll(ids); + foreach (var dataType in dataTypes) + _publishedDataTypes[dataType.Id] = CreatePublishedDataType(dataType); + } } } + + private PublishedDataType CreatePublishedDataType(IDataType dataType) + => new PublishedDataType(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy(() => dataType.Configuration)); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index a5669d1a9e..749b37a41a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -11,14 +11,14 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Initializes a new instance of the class. /// - public PublishedCultureInfo(string culture, string name, DateTime date) + public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date) { if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); Culture = culture; Name = name; - UrlSegment = name.ToUrlSegment(culture); + UrlSegment = urlSegment; Date = date; } diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs index 55b799244e..f4eb955c64 100644 --- a/src/Umbraco.Core/Models/RedirectUrl.cs +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -20,18 +19,6 @@ namespace Umbraco.Core.Models CreateDateUtc = DateTime.UtcNow; } - private static readonly Lazy Ps = new Lazy(); - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo ContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentId); - public readonly PropertyInfo ContentKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ContentKey); - public readonly PropertyInfo CreateDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDateUtc); - public readonly PropertyInfo CultureSelector = ExpressionHelper.GetPropertyInfo(x => x.Culture); - public readonly PropertyInfo UrlSelector = ExpressionHelper.GetPropertyInfo(x => x.Url); - } - private int _contentId; private Guid _contentKey; private DateTime _createDateUtc; @@ -41,36 +28,36 @@ namespace Umbraco.Core.Models /// public int ContentId { - get { return _contentId; } - set { SetPropertyValueAndDetectChanges(value, ref _contentId, Ps.Value.ContentIdSelector); } + get => _contentId; + set => SetPropertyValueAndDetectChanges(value, ref _contentId, nameof(ContentId)); } /// public Guid ContentKey { - get { return _contentKey; } - set { SetPropertyValueAndDetectChanges(value, ref _contentKey, Ps.Value.ContentKeySelector); } + get => _contentKey; + set => SetPropertyValueAndDetectChanges(value, ref _contentKey, nameof(ContentKey)); } /// public DateTime CreateDateUtc { - get { return _createDateUtc; } - set { SetPropertyValueAndDetectChanges(value, ref _createDateUtc, Ps.Value.CreateDateUtcSelector); } + get => _createDateUtc; + set => SetPropertyValueAndDetectChanges(value, ref _createDateUtc, nameof(CreateDateUtc)); } /// public string Culture { - get { return _culture; } - set { SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); } + get => _culture; + set => SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); } /// public string Url { - get { return _url; } - set { SetPropertyValueAndDetectChanges(value, ref _url, Ps.Value.UrlSelector); } + get => _url; + set => SetPropertyValueAndDetectChanges(value, ref _url, nameof(Url)); } } } diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index 2d2b05dbd6..f5d13c70c4 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -1,8 +1,6 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { @@ -25,16 +23,7 @@ namespace Umbraco.Core.Models _childId = childId; _relationType = relationType; } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo ChildIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildId); - public readonly PropertyInfo RelationTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RelationType); - public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); - } + /// /// Gets or sets the Parent Id of the Relation (Source) @@ -42,8 +31,8 @@ namespace Umbraco.Core.Models [DataMember] public int ParentId { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } + get => _parentId; + set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); } /// @@ -52,8 +41,8 @@ namespace Umbraco.Core.Models [DataMember] public int ChildId { - get { return _childId; } - set { SetPropertyValueAndDetectChanges(value, ref _childId, Ps.Value.ChildIdSelector); } + get => _childId; + set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId)); } /// @@ -62,8 +51,8 @@ namespace Umbraco.Core.Models [DataMember] public IRelationType RelationType { - get { return _relationType; } - set { SetPropertyValueAndDetectChanges(value, ref _relationType, Ps.Value.RelationTypeSelector); } + get => _relationType; + set => SetPropertyValueAndDetectChanges(value, ref _relationType, nameof(RelationType)); } /// @@ -72,18 +61,14 @@ namespace Umbraco.Core.Models [DataMember] public string Comment { - get { return _comment; } - set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } + get => _comment; + set => SetPropertyValueAndDetectChanges(value, ref _comment, nameof(Comment)); } /// /// Gets the Id of the that this Relation is based on. /// [IgnoreDataMember] - public int RelationTypeId - { - get { return _relationType.Id; } - } - + public int RelationTypeId => _relationType.Id; } } diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 5aa2c19ce3..259b7bc4ef 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -1,9 +1,7 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { @@ -36,25 +34,14 @@ namespace Umbraco.Core.Models Name = name; } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo IsBidirectionalSelector = ExpressionHelper.GetPropertyInfo(x => x.IsBidirectional); - public readonly PropertyInfo ParentObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentObjectType); - public readonly PropertyInfo ChildObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildObjectType); - } - /// /// Gets or sets the Name of the RelationType /// [DataMember] public string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } /// @@ -63,8 +50,8 @@ namespace Umbraco.Core.Models [DataMember] public string Alias { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } + get => _alias; + set => SetPropertyValueAndDetectChanges(value, ref _alias, nameof(Alias)); } /// @@ -73,8 +60,8 @@ namespace Umbraco.Core.Models [DataMember] public bool IsBidirectional { - get { return _isBidrectional; } - set { SetPropertyValueAndDetectChanges(value, ref _isBidrectional, Ps.Value.IsBidirectionalSelector); } + get => _isBidrectional; + set => SetPropertyValueAndDetectChanges(value, ref _isBidrectional, nameof(IsBidirectional)); } /// @@ -84,8 +71,8 @@ namespace Umbraco.Core.Models [DataMember] public Guid ParentObjectType { - get { return _parentObjectType; } - set { SetPropertyValueAndDetectChanges(value, ref _parentObjectType, Ps.Value.ParentObjectTypeSelector); } + get => _parentObjectType; + set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType)); } /// @@ -95,8 +82,8 @@ namespace Umbraco.Core.Models [DataMember] public Guid ChildObjectType { - get { return _childObjectType; } - set { SetPropertyValueAndDetectChanges(value, ref _childObjectType, Ps.Value.ChildObjectTypeSelector); } + get => _childObjectType; + set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType)); } } diff --git a/src/Umbraco.Core/Models/Trees/IBackOfficeSection.cs b/src/Umbraco.Core/Models/Sections/ISection.cs similarity index 80% rename from src/Umbraco.Core/Models/Trees/IBackOfficeSection.cs rename to src/Umbraco.Core/Models/Sections/ISection.cs index 86e2a18fd5..7967a9d01a 100644 --- a/src/Umbraco.Core/Models/Trees/IBackOfficeSection.cs +++ b/src/Umbraco.Core/Models/Sections/ISection.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core.Models.Trees +namespace Umbraco.Core.Models.Sections { /// /// Defines a back office section. /// - public interface IBackOfficeSection + public interface ISection { /// /// Gets the alias of the section. diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index db0e9b8c3b..7dae5d5393 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Reflection; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -15,16 +14,6 @@ namespace Umbraco.Core.Models private bool _isActive; private bool _isMaster; - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); - public readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); - public readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); - public readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); - } - /// /// Initializes a new instance of the class. /// @@ -73,8 +62,8 @@ namespace Umbraco.Core.Models /// public string ServerAddress { - get { return _serverAddress; } - set { SetPropertyValueAndDetectChanges(value, ref _serverAddress, Ps.Value.ServerAddressSelector); } + get => _serverAddress; + set => SetPropertyValueAndDetectChanges(value, ref _serverAddress, nameof(ServerAddress)); } /// @@ -82,8 +71,8 @@ namespace Umbraco.Core.Models /// public string ServerIdentity { - get { return _serverIdentity; } - set { SetPropertyValueAndDetectChanges(value, ref _serverIdentity, Ps.Value.ServerIdentitySelector); } + get => _serverIdentity; + set => SetPropertyValueAndDetectChanges(value, ref _serverIdentity, nameof(ServerIdentity)); } /// @@ -91,8 +80,8 @@ namespace Umbraco.Core.Models /// public bool IsActive { - get { return _isActive; } - set { SetPropertyValueAndDetectChanges(value, ref _isActive, Ps.Value.IsActiveSelector); } + get => _isActive; + set => SetPropertyValueAndDetectChanges(value, ref _isActive, nameof(IsActive)); } /// @@ -100,19 +89,27 @@ namespace Umbraco.Core.Models /// public bool IsMaster { - get { return _isMaster; } - set { SetPropertyValueAndDetectChanges(value, ref _isMaster, Ps.Value.IsMasterSelector); } + get => _isMaster; + set => SetPropertyValueAndDetectChanges(value, ref _isMaster, nameof(IsMaster)); } /// /// Gets the date and time the registration was created. /// - public DateTime Registered { get { return CreateDate; } set { CreateDate = value; }} + public DateTime Registered + { + get => CreateDate; + set => CreateDate = value; + } /// /// Gets the date and time the registration was last accessed. /// - public DateTime Accessed { get { return UpdateDate; } set { UpdateDate = value; }} + public DateTime Accessed + { + get => UpdateDate; + set => UpdateDate = value; + } /// /// Converts the value of this instance to its equivalent string representation. diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs new file mode 100644 index 0000000000..5c81017ec8 --- /dev/null +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Implements . + /// + public class SimpleContentType : ISimpleContentType + { + /// + /// Initializes a new instance of the class. + /// + public SimpleContentType(IContentType contentType) + : this((IContentTypeBase)contentType) + { + DefaultTemplate = contentType.DefaultTemplate; + } + + /// + /// Initializes a new instance of the class. + /// + public SimpleContentType(IMediaType mediaType) + : this((IContentTypeBase)mediaType) + { } + + /// + /// Initializes a new instance of the class. + /// + public SimpleContentType(IMemberType memberType) + : this((IContentTypeBase)memberType) + { } + + private SimpleContentType(IContentTypeBase contentType) + { + if (contentType == null) throw new ArgumentNullException(nameof(contentType)); + + Id = contentType.Id; + Alias = contentType.Alias; + Variations = contentType.Variations; + Icon = contentType.Icon; + IsContainer = contentType.IsContainer; + Name = contentType.Name; + AllowedAsRoot = contentType.AllowedAsRoot; + IsElement = contentType.IsElement; + } + + /// + public string Alias { get; } + + public int Id { get; } + + /// + public ITemplate DefaultTemplate { get; } + + /// + public ContentVariation Variations { get; } + + /// + public string Icon { get; } + + /// + public bool IsContainer { get; } + + public string Name { get; } + + /// + public bool AllowedAsRoot { get; } + + /// + public bool IsElement { get; } + + /// + public bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false) + { + // non-exact validation: can accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + return Variations.ValidateVariation(culture, segment, false, wildcards, false); + } + + protected bool Equals(SimpleContentType other) + { + return string.Equals(Alias, other.Alias) && Id == other.Id; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((SimpleContentType) obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Alias != null ? Alias.GetHashCode() : 0) * 397) ^ Id; + } + } + + // we have to have all this, because we're an IUmbracoEntity, because that is + // required by the query expression visitor / SimpleContentTypeMapper + + string ITreeEntity.Name { get => this.Name; set => throw new NotImplementedException(); } + int IEntity.Id { get => this.Id; set => throw new NotImplementedException(); } + bool IEntity.HasIdentity => this.Id != default; + int ITreeEntity.CreatorId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + int ITreeEntity.ParentId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + int ITreeEntity.Level { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + string ITreeEntity.Path { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + int ITreeEntity.SortOrder { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + bool ITreeEntity.Trashed => throw new NotImplementedException(); + Guid IEntity.Key { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + DateTime IEntity.CreateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + DateTime IEntity.UpdateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + DateTime? IEntity.DeleteDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + void ITreeEntity.SetParent(ITreeEntity parent) => throw new NotImplementedException(); + object IDeepCloneable.DeepClone() => throw new NotImplementedException(); + bool IRememberBeingDirty.WasDirty() => throw new NotImplementedException(); + bool IRememberBeingDirty.WasPropertyDirty(string propertyName) => throw new NotImplementedException(); + void IRememberBeingDirty.ResetWereDirtyProperties() => throw new NotImplementedException(); + void IRememberBeingDirty.ResetDirtyProperties(bool rememberDirty) => throw new NotImplementedException(); + IEnumerable IRememberBeingDirty.GetWereDirtyProperties() => throw new NotImplementedException(); + bool ICanBeDirty.IsDirty() => throw new NotImplementedException(); + bool ICanBeDirty.IsPropertyDirty(string propName) => throw new NotImplementedException(); + IEnumerable ICanBeDirty.GetDirtyProperties() => throw new NotImplementedException(); + void ICanBeDirty.ResetDirtyProperties() => throw new NotImplementedException(); + } +} diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index 1601ca3e76..089f89deb6 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -25,14 +24,6 @@ namespace Umbraco.Core.Models _value = value; } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - } - /// /// The CSS rule name that can be used by Umbraco in the back office /// @@ -43,8 +34,8 @@ namespace Umbraco.Core.Models /// public string Alias { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } + get => _alias; + set => SetPropertyValueAndDetectChanges(value, ref _alias, nameof(Alias)); } /// @@ -52,8 +43,8 @@ namespace Umbraco.Core.Models /// public string Value { - get { return _value; } - set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } + get => _value; + set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); } } diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index e9707e587d..315904493e 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -12,8 +11,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Tag : EntityBase, ITag { - private static PropertySelectors _selectors; - private string _group; private string _text; private int? _languageId; @@ -35,34 +32,25 @@ namespace Umbraco.Core.Models LanguageId = languageId; } - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - - private class PropertySelectors - { - public readonly PropertyInfo Group = ExpressionHelper.GetPropertyInfo(x => x.Group); - public readonly PropertyInfo Text = ExpressionHelper.GetPropertyInfo(x => x.Text); - public readonly PropertyInfo LanguageId = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); - } - /// public string Group { get => _group; - set => SetPropertyValueAndDetectChanges(value, ref _group, Selectors.Group); + set => SetPropertyValueAndDetectChanges(value, ref _group, nameof(Group)); } /// public string Text { get => _text; - set => SetPropertyValueAndDetectChanges(value, ref _text, Selectors.Text); + set => SetPropertyValueAndDetectChanges(value, ref _text, nameof(Text)); } /// public int? LanguageId { get => _languageId; - set => SetPropertyValueAndDetectChanges(value, ref _languageId, Selectors.LanguageId); + set => SetPropertyValueAndDetectChanges(value, ref _languageId, nameof(LanguageId)); } /// diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index b7e67c45ea..db473972e3 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Strings; @@ -17,16 +16,6 @@ namespace Umbraco.Core.Models private string _masterTemplateAlias; private Lazy _masterTemplateId; - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); - public readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.MasterTemplateId); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - } - public Template(string name, string alias) : this(name, alias, (Func) null) { } @@ -42,28 +31,28 @@ namespace Umbraco.Core.Models [DataMember] public Lazy MasterTemplateId { - get { return _masterTemplateId; } - set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, Ps.Value.MasterTemplateIdSelector); } + get => _masterTemplateId; + set => SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, nameof(MasterTemplateId)); } public string MasterTemplateAlias { - get { return _masterTemplateAlias; } - set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, Ps.Value.MasterTemplateAliasSelector); } + get => _masterTemplateAlias; + set => SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, nameof(MasterTemplateAlias)); } [DataMember] public new string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } [DataMember] public new string Alias { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.UnderscoreAlias), ref _alias, Ps.Value.AliasSelector); } + get => _alias; + set => SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.UnderscoreAlias), ref _alias, nameof(Alias)); } /// diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index d7266f77fe..093acef5b5 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; @@ -24,40 +23,28 @@ namespace Umbraco.Core.Models private int? _languageId; private string _domainName; - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId); - public readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); - public readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName); - } - [DataMember] public int? LanguageId { - get { return _languageId; } - set { SetPropertyValueAndDetectChanges(value, ref _languageId, Ps.Value.DefaultLanguageSelector); } + get => _languageId; + set => SetPropertyValueAndDetectChanges(value, ref _languageId, nameof(LanguageId)); } [DataMember] public string DomainName { - get { return _domainName; } - set { SetPropertyValueAndDetectChanges(value, ref _domainName, Ps.Value.DomainNameSelector); } + get => _domainName; + set => SetPropertyValueAndDetectChanges(value, ref _domainName, nameof(DomainName)); } [DataMember] public int? RootContentId { - get { return _contentId; } - set { SetPropertyValueAndDetectChanges(value, ref _contentId, Ps.Value.ContentSelector); } + get => _contentId; + set => SetPropertyValueAndDetectChanges(value, ref _contentId, nameof(RootContentId)); } - public bool IsWildcard - { - get { return string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); } - } + public bool IsWildcard => string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); /// /// Readonly value of the language ISO code for the domain diff --git a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs index 8dccf73814..83034a7e1b 100644 --- a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs +++ b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs @@ -43,6 +43,7 @@ namespace Umbraco.Core.Packaging Name = package.Element("name")?.Value, Author = author.Element("name")?.Value, AuthorUrl = author.Element("website")?.Value, + Contributors = info.Element("contributors")?.Elements("contributor").Select(x => x.Value).ToList() ?? new List(), Version = package.Element("version")?.Value, Readme = info.Element("readme")?.Value, License = package.Element("license")?.Value, diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs index a569954629..c811f484bc 100644 --- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -102,8 +102,8 @@ namespace Umbraco.Core.Packaging { // TODO: I don't think this ordering is necessary var orderedTypes = (from contentType in contentTypes - orderby contentType.ParentId descending, contentType.Id descending - select contentType).ToList(); + orderby contentType.ParentId descending, contentType.Id descending + select contentType).ToList(); removedContentTypes.AddRange(orderedTypes); contentTypeService.Delete(orderedTypes, userId); } @@ -157,7 +157,7 @@ namespace Umbraco.Core.Packaging DictionaryItemsUninstalled = removedDictionaryItems, DataTypesUninstalled = removedDataTypes, LanguagesUninstalled = removedLanguages, - + }; return summary; @@ -188,8 +188,8 @@ namespace Umbraco.Core.Packaging var element = packageDocument.XmlData; var roots = from doc in element.Elements() - where (string)doc.Attribute("isDoc") == "" - select doc; + where (string)doc.Attribute("isDoc") == "" + select doc; var contents = ParseDocumentRootXml(roots, parentId, importedDocumentTypes).ToList(); if (contents.Any()) @@ -289,13 +289,26 @@ namespace Umbraco.Core.Packaging var nodeName = element.Attribute("nodeName").Value; var path = element.Attribute("path").Value; var templateId = element.AttributeValue("template"); - + var properties = from property in element.Elements() where property.Attribute("isDoc") == null select property; + //TODO: This will almost never work, we can't reference a template by an INT Id within a package manifest, we need to change the + // packager to package templates by UDI and resolve by the same, in 98% of cases, this isn't going to work, or it will resolve the wrong template. var template = templateId.HasValue ? _fileService.GetTemplate(templateId.Value) : null; + //now double check this is correct since its an INT it could very well be pointing to an invalid template :/ + if (template != null) + { + if (!contentType.IsAllowedTemplate(template.Alias)) + { + //well this is awkward, we'll set the template to null and it will be wired up to the default template + // when it's persisted in the document repository + template = null; + } + } + IContent content = parent == null ? new Content(nodeName, parentId, contentType) { @@ -312,6 +325,12 @@ namespace Umbraco.Core.Packaging Key = key }; + //Here we make sure that we take composition properties in account as well + //otherwise we would skip them and end up losing content + var propTypes = contentType.CompositionPropertyTypes.Any() + ? contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x) + : contentType.PropertyTypes.ToDictionary(x => x.Alias, x => x); + foreach (var property in properties) { string propertyTypeAlias = property.Name.LocalName; @@ -319,10 +338,11 @@ namespace Umbraco.Core.Packaging { var propertyValue = property.Value; - var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias); - - //set property value - content.SetValue(propertyTypeAlias, propertyValue); + if (propTypes.TryGetValue(propertyTypeAlias, out var propertyType)) + { + //set property value + content.SetValue(propertyTypeAlias, propertyValue); + } } } @@ -335,7 +355,7 @@ namespace Umbraco.Core.Packaging public IEnumerable ImportDocumentType(XElement docTypeElement, int userId) { - return ImportDocumentTypes(new []{ docTypeElement }, userId); + return ImportDocumentTypes(new[] { docTypeElement }, userId); } /// @@ -359,7 +379,7 @@ namespace Umbraco.Core.Packaging public IEnumerable ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId) { var importedContentTypes = new Dictionary(); - + //When you are importing a single doc type we have to assume that the dependencies are already there. //Otherwise something like uSync won't work. var graph = new TopoGraph>(x => x.Key, x => x.Dependencies); @@ -452,7 +472,7 @@ namespace Umbraco.Core.Packaging if (updatedContentTypes.Any()) _contentTypeService.Save(updatedContentTypes, userId); } - + return list; } @@ -715,7 +735,7 @@ namespace Umbraco.Core.Packaging property.Element("Name").Value, dataTypeDefinitionId, property.Element("Type").Value.Trim()); //convert to a label! - dataTypeDefinition = _dataTypeService.GetByEditorAlias(Constants.PropertyEditors.Aliases.NoEdit).FirstOrDefault(); + dataTypeDefinition = _dataTypeService.GetByEditorAlias(Constants.PropertyEditors.Aliases.Label).FirstOrDefault(); //if for some odd reason this isn't there then ignore if (dataTypeDefinition == null) continue; } @@ -854,7 +874,7 @@ namespace Umbraco.Core.Packaging { _dataTypeService.Save(dataTypes, userId, true); } - + return dataTypes; } @@ -937,7 +957,7 @@ namespace Umbraco.Core.Packaging var items = new List(); foreach (var dictionaryItemElement in dictionaryItemElementList) items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId, userId)); - + return items; } @@ -1024,7 +1044,7 @@ namespace Umbraco.Core.Packaging _localizationService.Save(langauge, userId); list.Add(langauge); } - + return list; } @@ -1187,7 +1207,7 @@ namespace Umbraco.Core.Packaging public IEnumerable ImportTemplate(XElement templateElement, int userId) { - return ImportTemplates(new[] {templateElement}, userId); + return ImportTemplates(new[] { templateElement }, userId); } /// @@ -1234,7 +1254,7 @@ namespace Umbraco.Core.Packaging var alias = templateElement.Element("Alias").Value; var design = templateElement.Element("Design").Value; var masterElement = templateElement.Element("Master"); - + var existingTemplate = _fileService.GetTemplate(alias) as Template; var template = existingTemplate ?? new Template(templateName, alias); template.Content = design; diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs index b66cdb095c..50cb692530 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs @@ -39,6 +39,7 @@ namespace Umbraco.Core.Packaging LicenseUrl = xml.Element("license")?.AttributeValue("url") ?? string.Empty, Author = xml.Element("author")?.Value ?? string.Empty, AuthorUrl = xml.Element("author")?.AttributeValue("url") ?? string.Empty, + Contributors = xml.Element("contributors")?.Elements("contributor").Select(x => x.Value).ToList() ?? new List(), Readme = xml.Element("readme")?.Value ?? string.Empty, Actions = xml.Element("actions")?.ToString(SaveOptions.None) ?? "", //take the entire outer xml value ContentNodeId = xml.Element("content")?.AttributeValue("nodeId") ?? string.Empty, @@ -87,6 +88,8 @@ namespace Umbraco.Core.Packaging new XCData(def.Author ?? string.Empty), new XAttribute("url", def.AuthorUrl ?? string.Empty)), + new XElement("contributors", (def.Contributors ?? Array.Empty()).Where(x => !x.IsNullOrWhiteSpace()).Select(x => new XElement("contributor", x))), + new XElement("readme", new XCData(def.Readme ?? string.Empty)), actionsXml, new XElement("datatypes", string.Join(",", def.DataTypes ?? Array.Empty())), diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 05f67b3ed6..72954b238d 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -573,12 +573,25 @@ namespace Umbraco.Core.Packaging package.Add(requirements); info.Add(package); - //Author + // Author var author = new XElement("author", ""); author.Add(new XElement("name", definition.Author)); author.Add(new XElement("website", definition.AuthorUrl)); info.Add(author); + // Contributors + var contributors = new XElement("contributors", ""); + + if (definition.Contributors != null && definition.Contributors.Any()) + { + foreach (var contributor in definition.Contributors) + { + contributors.Add(new XElement("contributor", contributor)); + } + } + + info.Add(contributors); + info.Add(new XElement("readme", new XCData(definition.Readme))); return info; @@ -608,10 +621,5 @@ namespace Umbraco.Core.Packaging var packagesXml = XDocument.Load(packagesFile); return packagesXml; } - - - - - } } diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index 7898a9997a..b62a99ce83 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -5,17 +5,16 @@ namespace Umbraco.Core { public static class DatabaseSchema { + //TODO: Why aren't all table names with the same prefix? public const string TableNamePrefix = "umbraco"; public static class Tables { - public const string Lock = /*TableNamePrefix*/ "umbraco" + "Lock"; - public const string Log = /*TableNamePrefix*/ "umbraco" + "Log"; + public const string Lock = TableNamePrefix + "Lock"; + public const string Log = TableNamePrefix + "Log"; - public const string Node = /*TableNamePrefix*/ "umbraco" + "Node"; + public const string Node = TableNamePrefix + "Node"; public const string NodeData = /*TableNamePrefix*/ "cms" + "ContentNu"; - public const string NodeXml = /*TableNamePrefix*/ "cms" + "ContentXml"; // TODO: get rid of these with the xml cache - public const string NodePreviewXml = /*TableNamePrefix*/ "cms" + "PreviewXml"; // TODO: get rid of these with the xml cache public const string ContentType = /*TableNamePrefix*/ "cms" + "ContentType"; public const string ContentChildType = /*TableNamePrefix*/ "cms" + "ContentTypeAllowedContentType"; @@ -37,22 +36,22 @@ namespace Umbraco.Core public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup"; public const string PropertyData = TableNamePrefix + "PropertyData"; - public const string RelationType = /*TableNamePrefix*/ "umbraco" + "RelationType"; - public const string Relation = /*TableNamePrefix*/ "umbraco" + "Relation"; + public const string RelationType = TableNamePrefix + "RelationType"; + public const string Relation = TableNamePrefix + "Relation"; - public const string Domain = /*TableNamePrefix*/ "umbraco" + "Domains"; - public const string Language = /*TableNamePrefix*/ "umbraco" + "Language"; + public const string Domain = TableNamePrefix + "Domain"; + public const string Language = TableNamePrefix + "Language"; public const string DictionaryEntry = /*TableNamePrefix*/ "cms" + "Dictionary"; public const string DictionaryValue = /*TableNamePrefix*/ "cms" + "LanguageText"; - public const string User = /*TableNamePrefix*/ "umbraco" + "User"; - public const string UserGroup = /*TableNamePrefix*/ "umbraco" + "UserGroup"; - public const string UserStartNode = /*TableNamePrefix*/ "umbraco" + "UserStartNode"; - public const string User2UserGroup = /*TableNamePrefix*/ "umbraco" + "User2UserGroup"; - public const string User2NodeNotify = /*TableNamePrefix*/ "umbraco" + "User2NodeNotify"; - public const string UserGroup2App = /*TableNamePrefix*/ "umbraco" + "UserGroup2App"; - public const string UserGroup2NodePermission = /*TableNamePrefix*/ "umbraco" + "UserGroup2NodePermission"; - public const string ExternalLogin = /*TableNamePrefix*/ "umbraco" + "ExternalLogin"; + public const string User = TableNamePrefix + "User"; + public const string UserGroup = TableNamePrefix + "UserGroup"; + public const string UserStartNode = TableNamePrefix + "UserStartNode"; + public const string User2UserGroup = TableNamePrefix + "User2UserGroup"; + public const string User2NodeNotify = TableNamePrefix + "User2NodeNotify"; + public const string UserGroup2App = TableNamePrefix + "UserGroup2App"; + public const string UserGroup2NodePermission = TableNamePrefix + "UserGroup2NodePermission"; + public const string ExternalLogin = TableNamePrefix + "ExternalLogin"; public const string Macro = /*TableNamePrefix*/ "cms" + "Macro"; public const string MacroProperty = /*TableNamePrefix*/ "cms" + "MacroProperty"; @@ -61,21 +60,21 @@ namespace Umbraco.Core public const string MemberType = /*TableNamePrefix*/ "cms" + "MemberType"; public const string Member2MemberGroup = /*TableNamePrefix*/ "cms" + "Member2MemberGroup"; - public const string Access = /*TableNamePrefix*/ "umbraco" + "Access"; - public const string AccessRule = /*TableNamePrefix*/ "umbraco" + "AccessRule"; - public const string RedirectUrl = /*TableNamePrefix*/ "umbraco" + "RedirectUrl"; + public const string Access = TableNamePrefix + "Access"; + public const string AccessRule = TableNamePrefix + "AccessRule"; + public const string RedirectUrl = TableNamePrefix + "RedirectUrl"; - public const string CacheInstruction = /*TableNamePrefix*/ "umbraco" + "CacheInstruction"; - public const string Server = /*TableNamePrefix*/ "umbraco" + "Server"; + public const string CacheInstruction = TableNamePrefix + "CacheInstruction"; + public const string Server = TableNamePrefix + "Server"; public const string Tag = /*TableNamePrefix*/ "cms" + "Tags"; public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship"; public const string KeyValue = TableNamePrefix + "KeyValue"; - public const string AuditEntry = /*TableNamePrefix*/ "umbraco" + "Audit"; - public const string Consent = /*TableNamePrefix*/ "umbraco" + "Consent"; - public const string UserLogin = /*TableNamePrefix*/ "umbraco" + "UserLogin"; + public const string AuditEntry = TableNamePrefix + "Audit"; + public const string Consent = TableNamePrefix + "Consent"; + public const string UserLogin = TableNamePrefix + "UserLogin"; } } } diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index 20586cb6f4..835f76f9f9 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -76,7 +76,7 @@ namespace Umbraco.Core.Persistence do { c = unwrapped; - if (unwrapped is ProfiledDbConnection profiled) unwrapped = profiled.InnerConnection; + if (unwrapped is ProfiledDbConnection profiled) unwrapped = profiled.WrappedConnection; if (unwrapped is RetryDbConnection retrying) unwrapped = retrying.Inner; } while (c != unwrapped); diff --git a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs index e03efa8fe9..d7d02631b7 100644 --- a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs @@ -30,11 +30,14 @@ namespace Umbraco.Core.Persistence.Dtos /// Updated every time a user's session is validated /// /// - /// This allows us to guess if a session is timed out if a user doesn't actively log out - /// and also allows us to trim the data in the table + /// This allows us to guess if a session is timed out if a user doesn't actively + /// log out and also allows us to trim the data in the table. + /// The index is IMPORTANT as it prevents deadlocks during deletion of + /// old sessions (DELETE ... WHERE lastValidatedUtc < date). /// [Column("lastValidatedUtc")] [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.NonClustered, Name = "IX_userLoginDto_lastValidatedUtc")] public DateTime LastValidatedUtc { get; set; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index 01b0e59a1d..f5144e34eb 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -2,6 +2,7 @@ using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; @@ -10,10 +11,14 @@ namespace Umbraco.Core.Persistence.Factories { internal static class DataTypeFactory { - public static IDataType BuildEntity(DataTypeDto dto, PropertyEditorCollection editors) + public static IDataType BuildEntity(DataTypeDto dto, PropertyEditorCollection editors, ILogger logger) { if (!editors.TryGet(dto.EditorAlias, out var editor)) - throw new InvalidOperationException($"Could not find an editor with alias \"{dto.EditorAlias}\"."); + { + logger.Warn(typeof(DataTypeFactory), "Could not find an editor with alias {EditorAlias}, converting to label", dto.EditorAlias); + //convert to label + editor = new LabelPropertyEditor(logger); + } var dataType = new DataType(editor); diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 8e3c7db3b0..c7ce98a89c 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -78,7 +78,7 @@ namespace Umbraco.Core.Persistence.Factories var propertyGroups = new PropertyGroupCollection(); foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) { - var group = new PropertyGroup(MemberType.IsPublishingConst); + var group = new PropertyGroup(MemberType.SupportsPublishingConst); // if the group is defined on the current member type, // assign its identifier, else it will be zero @@ -93,7 +93,7 @@ namespace Umbraco.Core.Persistence.Factories group.Key = groupDto.UniqueId; group.Name = groupDto.Text; group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(MemberType.IsPublishingConst); + group.PropertyTypes = new PropertyTypeCollection(MemberType.SupportsPublishingConst); //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded var localGroupDto = groupDto; diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index d83650d798..f1473b5888 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -121,7 +121,7 @@ namespace Umbraco.Core.Persistence.Factories foreach (var property in properties) { - if (property.PropertyType.IsPublishing) + if (property.PropertyType.SupportsPublishing) { //create the resulting hashset if it's not created and the entity varies by culture if (entityVariesByCulture && editedCultures == null) diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs index 80819933f5..e20f7c1911 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core.Persistence.Mappers Add(); Add(); Add(); + Add(); Add(); Add(); Add(); diff --git a/src/Umbraco.Core/Persistence/Mappers/SimpleContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/SimpleContentTypeMapper.cs new file mode 100644 index 0000000000..3ad975defb --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/SimpleContentTypeMapper.cs @@ -0,0 +1,38 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(ISimpleContentType))] + [MapperFor(typeof(SimpleContentType))] + public sealed class SimpleContentTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); + CacheMap(src => src.Description, dto => dto.Description); + CacheMap(src => src.Icon, dto => dto.Icon); + CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.IsElement, dto => dto.IsElement); + CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs index 09f754c47a..c9d85feb25 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -4,10 +4,7 @@ using System.Data; using System.Data.SqlClient; using System.Data.SqlServerCe; using System.Linq; -using System.Text; -using System.Threading.Tasks; using NPoco; -using StackExchange.Profiling.Data; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs index 248f91284f..acfa51f895 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs @@ -147,8 +147,23 @@ namespace Umbraco.Core.Persistence private static TConnection GetTypedConnection(IDbConnection connection) where TConnection : class, IDbConnection { - var profiled = connection as ProfiledDbConnection; - return profiled == null ? connection as TConnection : profiled.InnerConnection as TConnection; + var c = connection; + for (;;) + { + switch (c) + { + case TConnection ofType: + return ofType; + case RetryDbConnection retry: + c = retry.Inner; + break; + case ProfiledDbConnection profiled: + c = profiled.WrappedConnection; + break; + default: + throw new NotSupportedException(connection.GetType().FullName); + } + } } /// @@ -160,8 +175,20 @@ namespace Umbraco.Core.Persistence private static TTransaction GetTypedTransaction(IDbTransaction transaction) where TTransaction : class, IDbTransaction { - var profiled = transaction as ProfiledDbTransaction; - return profiled == null ? transaction as TTransaction : profiled.WrappedTransaction as TTransaction; + var t = transaction; + for (;;) + { + switch (t) + { + case TTransaction ofType: + return ofType; + case ProfiledDbTransaction profiled: + t = profiled.WrappedTransaction; + break; + default: + throw new NotSupportedException(transaction.GetType().FullName); + } + } } /// @@ -173,11 +200,23 @@ namespace Umbraco.Core.Persistence private static TCommand GetTypedCommand(IDbCommand command) where TCommand : class, IDbCommand { - var faultHandling = command as FaultHandlingDbCommand; - if (faultHandling != null) command = faultHandling.Inner; - var profiled = command as ProfiledDbCommand; - if (profiled != null) command = profiled.InternalCommand; - return command as TCommand; + var c = command; + for (;;) + { + switch (c) + { + case TCommand ofType: + return ofType; + case FaultHandlingDbCommand faultHandling: + c = faultHandling.Inner; + break; + case ProfiledDbCommand profiled: + c = profiled.InternalCommand; + break; + default: + throw new NotSupportedException(command.GetType().FullName); + } + } } public static void TruncateTable(this IDatabase db, ISqlSyntaxProvider sqlSyntax, string tableName) diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index 23d818f0b3..a25890ff9d 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using NPoco; using Umbraco.Core.Persistence.Querying; @@ -1119,6 +1120,25 @@ namespace Umbraco.Core.Persistence #endregion + #region Aliasing + + internal static string GetAliasedField(this Sql sql, string field) + { + // get alias, if aliased + // + // regex looks for pattern "([\w+].[\w+]) AS ([\w+])" ie "(field) AS (alias)" + // and, if found & a group's field matches the field name, returns the alias + // + // so... if query contains "[umbracoNode].[nodeId] AS [umbracoNode__nodeId]" + // then GetAliased for "[umbracoNode].[nodeId]" returns "[umbracoNode__nodeId]" + + var matches = sql.SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL); + var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(field)); + return match == null ? field : match.Groups[2].Value; + } + + #endregion + #region Utilities private static string[] GetColumns(this Sql sql, string tableAlias = null, string referenceName = null, Expression>[] columnExpressions = null, bool withAlias = true) diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index a353f01f5b..bf26523d9e 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -60,7 +60,7 @@ namespace Umbraco.Core.Persistence.Querying if (m.Expression != null && m.Expression.Type != typeof(T) - && TypeHelper.IsTypeAssignableFrom(m.Expression.Type) + && TypeHelper.IsTypeAssignableFrom(m.Expression.Type) //TODO: Could this just be `IEntity` ? why does it need to be IUmbracoEntity, we aren't even using the reference to that below && EndsWithConstant(m) == false) { //if this is the case, it means we have a sub expression / nested property access, such as: x.ContentType.Alias == "Test"; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index a9bb097346..095c77eb42 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement _templateRepository = templateRepository; } - protected override bool IsPublishing => ContentType.IsPublishingConst; + protected override bool SupportsPublishing => ContentType.SupportsPublishingConst; protected override IRepositoryCachePolicy CreateCachePolicy() { @@ -60,11 +60,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ids.Any()) { //NOTE: This logic should never be executed according to our cache policy - return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, IsPublishing, this, _templateRepository) + return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository) .Where(x => ids.Contains(x.Id)); } - return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, IsPublishing, this, _templateRepository); + return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository); } protected override IEnumerable PerformGetAll(params Guid[] ids) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 7f41d2a456..e9b952f11d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : base(scopeAccessor, cache, logger) { } - protected abstract bool IsPublishing { get; } + protected abstract bool SupportsPublishing { get; } public IEnumerable> Move(TEntity moving, EntityContainer container) { @@ -1021,7 +1021,7 @@ AND umbracoNode.id <> @id", var dtos = Database .Fetch(sql); - var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, IsPublishing, id, createDate, updateDate,CreatePropertyType); + var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, SupportsPublishing, id, createDate, updateDate,CreatePropertyType); return new PropertyGroupCollection(propertyGroups); } @@ -1057,7 +1057,7 @@ AND umbracoNode.id <> @id", //Reset dirty properties Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false)); - return new PropertyTypeCollection(IsPublishing, list); + return new PropertyTypeCollection(SupportsPublishing, list); } protected void ValidateAlias(PropertyType pt) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 3de9e3c99b..02ba4d1c13 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -54,7 +54,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } var dtos = Database.Fetch(dataTypeSql); - return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value)).ToArray(); + return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value, Logger)).ToArray(); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -65,7 +65,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var dtos = Database.Fetch(sql); - return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value)).ToArray(); + return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value, Logger)).ToArray(); } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 6af7031883..24b50d294b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -120,6 +119,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement break; case QueryType.Single: case QueryType.Many: + // R# may flag this ambiguous and red-squiggle it, but it is not sql = sql.Select(r => r.Select(documentDto => documentDto.ContentDto, r1 => r1.Select(contentDto => contentDto.NodeDto)) @@ -202,10 +202,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement "DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentCultureVariation + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentVersion + " WHERE id IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)", "DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)", - "DELETE FROM cmsPreviewXml WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)", "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", - "DELETE FROM cmsContentXml WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id" @@ -380,9 +378,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' - foreach (var (culture, infos) in content.CultureInfos) - if (infos.Name != content.GetPublishName(culture)) - (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(culture); + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.CultureInfos) + if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) + (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); // insert content variations Database.BulkInsertRecords(GetContentVariationDtos(content, publishing)); @@ -541,11 +540,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' - foreach (var (culture, infos) in content.CultureInfos) - if (infos.Name != content.GetPublishName(culture)) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.CultureInfos) + if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) { edited = true; - (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(culture); + (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); // TODO: change tracking // at the moment, we don't do any dirty tracking on property values, so we don't know whether the @@ -1127,7 +1127,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // TODO: shall we get published properties or not? //var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0; - var publishedVersionId = dto.PublishedVersionDto != null ? dto.PublishedVersionDto.Id : 0; + var publishedVersionId = dto.PublishedVersionDto?.Id ?? 0; var temp = new TempContent(dto.NodeId, versionId, publishedVersionId, contentType); var ltemp = new List> { temp }; @@ -1187,12 +1187,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (contentVariations.TryGetValue(content.VersionId, out var contentVariation)) foreach (var v in contentVariation) content.SetCultureInfo(v.Culture, v.Name, v.Date); + if (content.PublishedVersionId > 0 && contentVariations.TryGetValue(content.PublishedVersionId, out contentVariation)) + { foreach (var v in contentVariation) content.SetPublishInfo(v.Culture, v.Name, v.Date); + } + if (documentVariations.TryGetValue(content.Id, out var documentVariation)) - foreach (var v in documentVariation.Where(x => x.Edited)) - content.SetCultureEdited(v.Culture); + content.SetCultureEdited(documentVariation.Where(x => x.Edited).Select(x => x.Culture)); } private IDictionary> GetContentVariations(List> temps) @@ -1262,14 +1265,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IEnumerable GetContentVariationDtos(IContent content, bool publishing) { // create dtos for the 'current' (non-published) version, all cultures - foreach (var (culture, name) in content.CultureInfos) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.CultureInfos) yield return new ContentVersionCultureVariationDto { VersionId = content.VersionId, - LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), - Culture = culture, - Name = name.Name, - UpdateDate = content.GetUpdateDate(culture) ?? DateTime.MinValue // we *know* there is a value + LanguageId = LanguageRepository.GetIdByIsoCode(cultureInfo.Culture) ?? throw new InvalidOperationException("Not a valid culture."), + Culture = cultureInfo.Culture, + Name = cultureInfo.Name, + UpdateDate = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue // we *know* there is a value }; // if not publishing, we're just updating the 'current' (non-published) version, @@ -1277,14 +1281,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (!publishing) yield break; // create dtos for the 'published' version, for published cultures (those having a name) - foreach (var (culture, name) in content.PublishCultureInfos) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.PublishCultureInfos) yield return new ContentVersionCultureVariationDto { VersionId = content.PublishedVersionId, - LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), - Culture = culture, - Name = name.Name, - UpdateDate = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value + LanguageId = LanguageRepository.GetIdByIsoCode(cultureInfo.Culture) ?? throw new InvalidOperationException("Not a valid culture."), + Culture = cultureInfo.Culture, + Name = cultureInfo.Name, + UpdateDate = content.GetPublishDate(cultureInfo.Culture) ?? DateTime.MinValue // we *know* there is a value }; } @@ -1344,7 +1349,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement EnsureVariantNamesAreUnique(content, publishing); } - private void EnsureInvariantNameExists(Content content) + private void EnsureInvariantNameExists(IContent content) { if (content.ContentType.VariesByCulture()) { @@ -1358,7 +1363,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var defaultCulture = LanguageRepository.GetDefaultIsoCode(); content.Name = defaultCulture != null && content.CultureInfos.TryGetValue(defaultCulture, out var cultureName) ? cultureName.Name - : content.CultureInfos.First().Value.Name; + : content.CultureInfos[0].Name; } else { @@ -1368,7 +1373,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } - private void EnsureInvariantNameIsUnique(Content content) + private void EnsureInvariantNameIsUnique(IContent content) { content.Name = EnsureUniqueNodeName(content.ParentId, content.Name, content.Id); } @@ -1405,25 +1410,26 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // of whether the name has changed (ie the culture has been updated) - some saving culture // fr-FR could cause culture en-UK name to change - not sure that is clean - foreach (var (culture, name) in content.CultureInfos) + foreach (var cultureInfo in content.CultureInfos) { - var langId = LanguageRepository.GetIdByIsoCode(culture); + var langId = LanguageRepository.GetIdByIsoCode(cultureInfo.Culture); if (!langId.HasValue) continue; if (!names.TryGetValue(langId.Value, out var cultureNames)) continue; // get a unique name var otherNames = cultureNames.Select(x => new SimilarNodeName { Id = x.Id, Name = x.Name }); - var uniqueName = SimilarNodeName.GetUniqueName(otherNames, 0, name.Name); + var uniqueName = SimilarNodeName.GetUniqueName(otherNames, 0, cultureInfo.Name); - if (uniqueName == content.GetCultureName(culture)) continue; + if (uniqueName == content.GetCultureName(cultureInfo.Culture)) continue; // update the name, and the publish name if published - content.SetCultureName(uniqueName, culture); - if (publishing && content.PublishCultureInfos.ContainsKey(culture)) - content.SetPublishInfo(culture, uniqueName, DateTime.Now); + content.SetCultureName(uniqueName, cultureInfo.Culture); + if (publishing && content.PublishCultureInfos.ContainsKey(cultureInfo.Culture)) + content.SetPublishInfo(cultureInfo.Culture, uniqueName, DateTime.Now); //TODO: This is weird, this call will have already been made in the SetCultureName } } + // ReSharper disable once ClassNeverInstantiated.Local private class CultureNodeName { public int Id { get; set; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs index 01b8bdd66c..69523a860a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs @@ -33,10 +33,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0"); + var sql = GetBaseQuery(false).Where("umbracoDomain.id > 0"); if (ids.Any()) { - sql.Where("umbracoDomains.id in (@ids)", new { ids = ids }); + sql.Where("umbracoDomain.id in (@ids)", new { ids = ids }); } return Database.Fetch(sql).Select(ConvertFromDto); @@ -56,7 +56,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } else { - sql.Select("umbracoDomains.*, umbracoLanguage.languageISOCode") + sql.Select("umbracoDomain.*, umbracoLanguage.languageISOCode") .From() .LeftJoin() .On(dto => dto.DefaultLanguage, dto => dto.Id); @@ -67,14 +67,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoDomains.id = @id"; + return "umbracoDomain.id = @id"; } protected override IEnumerable GetDeleteClauses() { var list = new List { - "DELETE FROM umbracoDomains WHERE id = @id" + "DELETE FROM umbracoDomain WHERE id = @id" }; return list; } @@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IDomain entity) { - var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName }); + var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomain WHERE domainName = @domainName", new { domainName = entity.DomainName }); if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); if (entity.RootContentId.HasValue) @@ -122,7 +122,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { ((UmbracoDomain)entity).UpdatingEntity(); - var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName AND umbracoDomains.id <> @id", + var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomain WHERE domainName = @domainName AND umbracoDomain.id <> @id", new { domainName = entity.DomainName, id = entity.Id }); //ensure there is no other domain with the same name on another entity if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 85912694f0..b4c0e51c22 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -66,18 +66,31 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //no matter what we always must have node id ordered at the end sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); - var page = Database.Page(pageIndex + 1, pageSize, sql); - var dtos = page.Items; + // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names + var pageIndexToFetch = pageIndex + 1; + IEnumerable dtos; + if(isContent) + { + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; + } + else + { + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; + } + var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); - + if (isContent) BuildVariants(entities.Cast()); // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media if (isMedia) - BuildProperties(entities, dtos); + BuildProperties(entities, dtos.ToList()); - totalRecords = page.TotalItems; return entities; } @@ -198,7 +211,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IEnumerable PerformGetAllPaths(Guid objectType, Action> filter = null) { - var sql = Sql().Select(x => x.NodeId, x => x.Path).From().Where(x => x.NodeObjectType == objectType); + // NodeId is named Id on TreeEntityPath = use an alias + var sql = Sql().Select(x => Alias(x.NodeId, nameof(TreeEntityPath.Id)), x => x.Path).From().Where(x => x.NodeObjectType == objectType); filter?.Invoke(sql); return Database.Fetch(sql); } @@ -405,7 +419,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) .WhereIn(x => x.VersionId, versionIds) .OrderBy(x => x.VersionId); - } + } // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index b3f2ff4af0..5a62c25df7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -250,6 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement lock (_codeIdMap) { if (_codeIdMap.TryGetValue(isoCode, out var id)) return id; + if (isoCode.Contains('-') && _codeIdMap.TryGetValue(isoCode.Split('-').First(), out var invariantId)) return invariantId; } if (throwOnNotFound) throw new ArgumentException($"Code {isoCode} does not correspond to an existing language.", nameof(isoCode)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index 5287af353a..a6f39bdb71 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : base(scopeAccessor, cache, logger) { } - protected override bool IsPublishing => MediaType.IsPublishingConst; + protected override bool SupportsPublishing => MediaType.SupportsPublishingConst; protected override IRepositoryCachePolicy CreateCachePolicy() { @@ -55,11 +55,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ids.Any()) { //NOTE: This logic should never be executed according to our cache policy - return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, IsPublishing, this) + return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this) .Where(x => ids.Contains(x.Id)); } - return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, IsPublishing, this); + return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this); } protected override IEnumerable PerformGetAll(params Guid[] ids) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index ba3526f1f0..808f61305a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -188,7 +188,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement "DELETE FROM cmsMember2MemberGroup WHERE Member = @id", "DELETE FROM cmsMember WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", - "DELETE FROM cmsContentXml WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", "DELETE FROM umbracoNode WHERE id = @id" }; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index 22a5059be7..afb6ac8b43 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : base(scopeAccessor, cache, logger) { } - protected override bool IsPublishing => MemberType.IsPublishingConst; + protected override bool SupportsPublishing => MemberType.SupportsPublishingConst; protected override IRepositoryCachePolicy CreateCachePolicy() { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index 2bad7229f2..6b2dfddaeb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IRepositoryCachePolicy CreateCachePolicy() { - // TODO: wtf are we doing with cache here? + // TODO: what are we doing with cache here? // why are we using disabled cache helper up there? // // 7.6 says: diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 07ad6bcba9..9e0999e38f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -702,23 +702,6 @@ ORDER BY colName"; { if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); - // get the referenced column name and find the corresp mapped column name - var expressionMember = ExpressionHelper.GetMemberInfo(orderBy); - var mapper = _mapperCollection[typeof(IUser)]; - var mappedField = mapper.Map(SqlContext.SqlSyntax, expressionMember.Name); - - if (mappedField.IsNullOrWhiteSpace()) - throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause"); - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, mappedField, orderDirection, includeUserGroups, excludeUserGroups, userState, filter); - } - - private IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection = Direction.Ascending, - string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null, IQuery filter = null) - { - if (string.IsNullOrWhiteSpace(orderBy)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(orderBy)); - Sql filterSql = null; var customFilterWheres = filter?.GetWhereClauses().ToArray(); var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0; @@ -734,7 +717,6 @@ ORDER BY colName"; filterSql.Append($"AND ({clause.Item1})", clause.Item2); } - if (includeUserGroups != null && includeUserGroups.Length > 0) { const string subQuery = @"AND (umbracoUser.id IN (SELECT DISTINCT umbracoUser.id @@ -808,7 +790,7 @@ ORDER BY colName"; sql = new SqlTranslator(sql, query).Translate(); // get sorted and filtered sql - var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderDirection, orderBy); + var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderBy, orderDirection); // get a page of results and total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); @@ -834,14 +816,36 @@ ORDER BY colName"; return sql; } - private static Sql ApplySort(Sql sql, Direction orderDirection, string orderBy) + private Sql ApplySort(Sql sql, Expression> orderBy, Direction orderDirection) { - if (string.IsNullOrEmpty(orderBy)) return sql; + if (orderBy == null) return sql; + + var expressionMember = ExpressionHelper.GetMemberInfo(orderBy); + var mapper = _mapperCollection[typeof(IUser)]; + var mappedField = mapper.Map(SqlContext.SqlSyntax, expressionMember.Name); + + if (mappedField.IsNullOrWhiteSpace()) + throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause"); + + // beware! NPoco paging code parses the query to isolate the ORDER BY fragment, + // using a regex that wants "([\w\.\[\]\(\)\s""`,]+)" - meaning that anything + // else in orderBy is going to break NPoco / not be detected + + // beware! NPoco paging code (in PagingHelper) collapses everything [foo].[bar] + // to [bar] only, so we MUST use aliases, cannot use [table].[field] + + // beware! pre-2012 SqlServer is using a convoluted syntax for paging, which + // includes "SELECT ROW_NUMBER() OVER (ORDER BY ...) poco_rn FROM SELECT (...", + // so anything added here MUST also be part of the inner SELECT statement, ie + // the original statement, AND must be using the proper alias, as the inner SELECT + // will hide the original table.field names entirely + + var orderByField = sql.GetAliasedField(mappedField); if (orderDirection == Direction.Ascending) - sql.OrderBy(orderBy); + sql.OrderBy(orderByField); else - sql.OrderByDescending(orderBy); + sql.OrderByDescending(orderByField); return sql; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 4e9417c760..e51aa547b8 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.Data.Common; using System.Linq; using NPoco; @@ -122,6 +123,12 @@ namespace Umbraco.Core.Persistence.SqlSyntax SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime, SERVERPROPERTY('ProductLevel') ProductLevel;"; + string GetString(IDataReader reader, int ordinal, string defaultValue) + => reader.IsDBNull(ordinal) ? defaultValue : reader.GetString(ordinal); + + int GetInt32(IDataReader reader, int ordinal, int defaultValue) + => reader.IsDBNull(ordinal) ? defaultValue : reader.GetInt32(ordinal); + connection.ConnectionString = connectionString; ServerVersionInfo version; using (connection) @@ -135,7 +142,13 @@ namespace Umbraco.Core.Persistence.SqlSyntax { reader.Read(); // InstanceName can be NULL for the default instance - version = new ServerVersionInfo(reader.GetString(0), reader.GetString(2), reader.IsDBNull(3) ? "(default)" : reader.GetString(3), (EngineEdition) reader.GetInt32(5), reader.GetString(7), reader.GetString(9)); + version = new ServerVersionInfo( + GetString(reader, 0, "Unknown"), + GetString(reader, 2, "(default)"), + GetString(reader, 3, string.Empty), + (EngineEdition) GetInt32(reader, 5, 0), + GetString(reader, 7, "DEFAULT"), + GetString(reader, 9, "Unknown")); } connection.Close(); } diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index eab0ae5509..dc86ff060c 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence /// // TODO: these comments are not true anymore // TODO: this class needs not be disposable! - internal class UmbracoDatabaseFactory : DisposableObject, IUmbracoDatabaseFactory + internal class UmbracoDatabaseFactory : DisposableObjectSlim, IUmbracoDatabaseFactory { private readonly Lazy _mappers; private readonly ILogger _logger; @@ -139,7 +139,7 @@ namespace Umbraco.Core.Persistence { // replace NPoco database type by a more efficient one - var setting = ConfigurationManager.AppSettings["Umbraco.DatabaseFactory.ServerVersion"]; + var setting = ConfigurationManager.AppSettings[Constants.AppSettings.Debug.DatabaseFactoryServerVersion]; var fromSettings = false; if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.") diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index 096b80de1f..c4380f032c 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -290,7 +290,7 @@ namespace Umbraco.Core.PropertyEditors /// public IEnumerable ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published) { - published &= property.PropertyType.IsPublishing; + published &= property.PropertyType.SupportsPublishing; var nodeName = property.PropertyType.Alias.ToSafeAlias(); diff --git a/src/Umbraco.Web/PropertyEditors/DateTimeConfiguration.cs b/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs similarity index 66% rename from src/Umbraco.Web/PropertyEditors/DateTimeConfiguration.cs rename to src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs index 39eec34ba6..9801ba0271 100644 --- a/src/Umbraco.Web/PropertyEditors/DateTimeConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs @@ -1,12 +1,13 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors +namespace Umbraco.Core.PropertyEditors { /// /// Represents the configuration for the datetime value editor. /// - public class DateTimeConfiguration : DateConfiguration + public class DateTimeConfiguration { + [ConfigurationField("format", "Date format", "textstring", Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] + public string Format { get; set; } + public DateTimeConfiguration() { // different default values @@ -16,4 +17,4 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("offsetTime", "Offset time", "boolean", Description = "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] public bool OffsetTime { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs index 2b8a207491..d0c40b1e63 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Represents a property editor for label properties. /// - [DataEditor(Constants.PropertyEditors.Aliases.NoEdit, "Label", "readonlyvalue", Icon = "icon-readonly")] + [DataEditor(Constants.PropertyEditors.Aliases.Label, "Label", "readonlyvalue", Icon = "icon-readonly")] public class LabelPropertyEditor : DataEditor { /// diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs index 4062ed7311..3d69c37b8b 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors.ValueConverters @@ -8,8 +9,6 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class CheckboxListValueConverter : PropertyValueConverterBase { - private static readonly char[] Comma = { ',' }; - public override bool IsConverter(PublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList); @@ -26,7 +25,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (string.IsNullOrEmpty(sourceString)) return Enumerable.Empty(); - return sourceString.Split(Comma, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); + return JsonConvert.DeserializeObject(sourceString); } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index fde9044108..ffe9feb653 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -8,14 +8,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class DatePickerValueConverter : PropertyValueConverterBase { - private static readonly string[] PropertyEditorAliases = - { - Constants.PropertyEditors.Aliases.DateTime, - Constants.PropertyEditors.Aliases.Date - }; - public override bool IsConverter(PublishedPropertyType propertyType) - => PropertyEditorAliases.Contains(propertyType.EditorAlias); + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.DateTime); public override Type GetPropertyValueType(PublishedPropertyType propertyType) => typeof (DateTime); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs index 1be37e9c94..05a5f15aaf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public class LabelValueConverter : PropertyValueConverterBase { public override bool IsConverter(PublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.NoEdit.Equals(propertyType.EditorAlias); + => Constants.PropertyEditors.Aliases.Label.Equals(propertyType.EditorAlias); public override Type GetPropertyValueType(PublishedPropertyType propertyType) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs index 362c88d08c..b99cc7e0e3 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs @@ -10,17 +10,17 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList); public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (int); + => typeof (string); public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) => PropertyCacheLevel.Element; public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - var intAttempt = source.TryConvertTo(); + var attempt = source.TryConvertTo(); - if (intAttempt.Success) - return intAttempt.Result; + if (attempt.Success) + return attempt.Result; return null; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index 09170b746a..8ad09733f8 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -36,6 +36,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (source is int) return (int)source == 1; + // this is required for correct true/false handling in nested content elements + if (source is long) + return (long)source == 1; + if (source is bool) return (bool)source; diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs new file mode 100644 index 0000000000..4e026490a4 --- /dev/null +++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs @@ -0,0 +1,33 @@ +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods for . + /// + public static class PublishedModelFactoryExtensions + { + /// + /// Executes an action with a safe live factory/ + /// + /// + /// If the factory is a live factory, ensures it is refreshed and locked while executing the action. + /// + public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action) + { + if (factory is ILivePublishedModelFactory liveFactory) + { + lock (liveFactory.SyncRoot) + { + liveFactory.Refresh(); + action(); + } + } + else + { + action(); + } + } + } +} diff --git a/src/Umbraco.Core/Composing/RegisterExtensions.cs b/src/Umbraco.Core/RegisterExtensions.cs similarity index 96% rename from src/Umbraco.Core/Composing/RegisterExtensions.cs rename to src/Umbraco.Core/RegisterExtensions.cs index d1eacc0c0f..69e092878e 100644 --- a/src/Umbraco.Core/Composing/RegisterExtensions.cs +++ b/src/Umbraco.Core/RegisterExtensions.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Composing +using Umbraco.Core.Composing; + +namespace Umbraco.Core { /// /// Provides extension methods to the class. diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreInitialComponent.cs similarity index 88% rename from src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs rename to src/Umbraco.Core/Runtime/CoreInitialComponent.cs index b9efdd6432..20fee7a299 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComponent.cs @@ -1,15 +1,15 @@ using System.Collections.Generic; using AutoMapper; -using Umbraco.Core.Components; +using Umbraco.Core.Composing; using Umbraco.Core.IO; namespace Umbraco.Core.Runtime { - public class CoreRuntimeComponent : IComponent + public class CoreInitialComponent : IComponent { private readonly IEnumerable _mapperProfiles; - public CoreRuntimeComponent(IEnumerable mapperProfiles) + public CoreInitialComponent(IEnumerable mapperProfiles) { _mapperProfiles = mapperProfiles; } diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs similarity index 91% rename from src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs rename to src/Umbraco.Core/Runtime/CoreInitialComposer.cs index ee289ddcfa..f32a46f3f8 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -1,15 +1,15 @@ using System; using System.Configuration; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.Composers; +using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Migrations; using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Migrations.PostMigrations; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PackageActions; using Umbraco.Core.Persistence; @@ -24,7 +24,9 @@ using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidato namespace Umbraco.Core.Runtime { - public class CoreRuntimeComposer : ComponentComposer, IRuntimeComposer + // core's initial composer composes before all core composers + [ComposeBefore(typeof(ICoreComposer))] + public class CoreInitialComposer : ComponentComposer { public override void Compose(Composition composition) { @@ -76,7 +78,7 @@ namespace Umbraco.Core.Runtime // TODO: this is a hack, use proper configuration! // also: we still register the full IServerMessenger because // even on 1 single server we can have 2 concurrent app domains - var singleServer = "true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"]); + var singleServer = "true".InvariantEquals(ConfigurationManager.AppSettings[Constants.AppSettings.DisableElectionForSingleServer]); return singleServer ? (IServerRegistrar) new SingleServerRegistrar(f.GetInstance()) : new DatabaseServerRegistrar( @@ -113,13 +115,13 @@ namespace Umbraco.Core.Runtime composition.WithCollectionBuilder() .Append(); - composition.WithCollectionBuilder() - .Add(() => composition.TypeLoader.GetTypes()); - composition.RegisterUnique(factory => new MigrationBuilder(factory)); // by default, register a noop factory composition.RegisterUnique(); + + // by default, register a noop rebuilder + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 9c18b3d8c3..f00365496a 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.Threading; using System.Web; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; @@ -113,8 +112,7 @@ namespace Umbraco.Core.Runtime var configs = GetConfigs(); // type loader - var localTempStorage = configs.Global().LocalTempStorageLocation; - var typeLoader = new TypeLoader(appCaches.RuntimeCache, localTempStorage, ProfilingLogger); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' @@ -170,7 +168,7 @@ namespace Umbraco.Core.Runtime _state.BootFailedException = bfe; } - timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves + timer?.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves // if something goes wrong above, we may end up with no factory // meaning nothing can get the runtime state, etc - so let's try @@ -244,7 +242,7 @@ namespace Umbraco.Core.Runtime } catch { - timer.Fail(); + timer?.Fail(); throw; } } @@ -271,7 +269,7 @@ namespace Umbraco.Core.Runtime { _state.Level = RuntimeLevel.BootFailed; _state.Reason = RuntimeLevelReason.BootFailedOnException; - timer.Fail(); + timer?.Fail(); throw; } } @@ -287,7 +285,7 @@ namespace Umbraco.Core.Runtime } catch { - timer.Fail(); + timer?.Fail(); throw; } } diff --git a/src/Umbraco.Core/RuntimeOptions.cs b/src/Umbraco.Core/RuntimeOptions.cs index 1f89ee6314..c0bae23446 100644 --- a/src/Umbraco.Core/RuntimeOptions.cs +++ b/src/Umbraco.Core/RuntimeOptions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Configuration; using System.Runtime.CompilerServices; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 9ddbb09748..55255b418d 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -242,7 +242,7 @@ namespace Umbraco.Core protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { - var upgrader = new UmbracoUpgrader(); + var upgrader = new Upgrader(new UmbracoPlan()); var stateValueKey = upgrader.StateValueKey; // no scope, no service - just directly accessing the database diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index f9ea02b072..e9dd04c5fa 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -75,7 +75,7 @@ namespace Umbraco.Core.Scoping // see note below if (scopeFileSystems == true) - _fscope = fileSystems.Shadow(Guid.NewGuid()); + _fscope = fileSystems.Shadow(); return; } @@ -105,7 +105,7 @@ namespace Umbraco.Core.Scoping // every scoped FS to trigger the creation of shadow FS "on demand", and that would be // pretty pointless since if scopeFileSystems is true, we *know* we want to shadow if (scopeFileSystems == true) - _fscope = fileSystems.Shadow(Guid.NewGuid()); + _fscope = fileSystems.Shadow(); } } diff --git a/src/Umbraco.Core/ServiceContextExtensions.cs b/src/Umbraco.Core/ServiceContextExtensions.cs index b1ca021173..3af918815f 100644 --- a/src/Umbraco.Core/ServiceContextExtensions.cs +++ b/src/Umbraco.Core/ServiceContextExtensions.cs @@ -6,15 +6,15 @@ namespace Umbraco.Core { public static class ServiceContextExtensions { - public static IContentTypeServiceBase GetContentTypeService(this ServiceContext services) + public static IContentTypeBaseService GetContentTypeService(this ServiceContext services) where T : IContentTypeComposition { if (typeof(T).Implements()) - return services.ContentTypeService as IContentTypeServiceBase; + return services.ContentTypeService as IContentTypeBaseService; if (typeof(T).Implements()) - return services.MediaTypeService as IContentTypeServiceBase; + return services.MediaTypeService as IContentTypeBaseService; if (typeof(T).Implements()) - return services.MemberTypeService as IContentTypeServiceBase; + return services.MemberTypeService as IContentTypeBaseService; throw new ArgumentException("Type " + typeof(T).FullName + " does not have a service."); } } diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 03a790d4b8..1175df81dc 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -31,16 +31,16 @@ namespace Umbraco.Core.Services /// /// /// - /// + /// /// /// - public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string mediaTypeAlias, int userId = 0) + public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { var guidUdi = parentId as GuidUdi; if (guidUdi == null) throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); var parent = contentService.GetById(guidUdi.Guid); - return contentService.Create(name, parent, mediaTypeAlias, userId); + return contentService.Create(name, parent, contentTypeAlias, userId); } /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 7fb7450b46..784d04864e 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -32,27 +32,27 @@ namespace Umbraco.Core.Services /// /// Saves a blueprint. /// - void SaveBlueprint(IContent content, int userId = 0); + void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId); /// /// Deletes a blueprint. /// - void DeleteBlueprint(IContent content, int userId = 0); + void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId); /// /// Creates a new content item from a blueprint. /// - IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0); + IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId); /// /// Deletes blueprints for a content type. /// - void DeleteBlueprintsOfType(int contentTypeId, int userId = 0); + void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId); /// /// Deletes blueprints for content types. /// - void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0); + void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId); #endregion @@ -237,13 +237,13 @@ namespace Umbraco.Core.Services /// /// Saves a document. /// - OperationResult Save(IContent content, int userId = 0, bool raiseEvents = true); + OperationResult Save(IContent content, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// /// Saves documents. /// // TODO: why only 1 result not 1 per content?! - OperationResult Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + OperationResult Save(IEnumerable contents, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// /// Deletes a document. @@ -252,7 +252,7 @@ namespace Umbraco.Core.Services /// This method will also delete associated media files, child content and possibly associated domains. /// This method entirely clears the content from the database. /// - OperationResult Delete(IContent content, int userId = 0); + OperationResult Delete(IContent content, int userId = Constants.Security.SuperUserId); /// /// Deletes all documents of a given document type. @@ -261,7 +261,7 @@ namespace Umbraco.Core.Services /// All non-deleted descendants of the deleted documents are moved to the recycle bin. /// This operation is potentially dangerous and expensive. /// - void DeleteOfType(int documentTypeId, int userId = 0); + void DeleteOfType(int documentTypeId, int userId = Constants.Security.SuperUserId); /// /// Deletes all documents of given document types. @@ -270,17 +270,17 @@ namespace Umbraco.Core.Services /// All non-deleted descendants of the deleted documents are moved to the recycle bin. /// This operation is potentially dangerous and expensive. /// - void DeleteOfTypes(IEnumerable contentTypeIds, int userId = 0); + void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId); /// /// Deletes versions of a document prior to a given date. /// - void DeleteVersions(int id, DateTime date, int userId = 0); + void DeleteVersions(int id, DateTime date, int userId = Constants.Security.SuperUserId); /// /// Deletes a version of a document. /// - void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = 0); + void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId); #endregion @@ -289,7 +289,7 @@ namespace Umbraco.Core.Services /// /// Moves a document under a new parent. /// - void Move(IContent content, int parentId, int userId = 0); + void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId); /// /// Copies a document. @@ -297,7 +297,7 @@ namespace Umbraco.Core.Services /// /// Recursively copies all children. /// - IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); + IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = Constants.Security.SuperUserId); /// /// Copies a document. @@ -305,12 +305,12 @@ namespace Umbraco.Core.Services /// /// Optionally recursively copies all children. /// - IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); + IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Constants.Security.SuperUserId); /// /// Moves a document to the recycle bin. /// - OperationResult MoveToRecycleBin(IContent content, int userId = 0); + OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId); /// /// Empties the recycle bin. @@ -320,12 +320,12 @@ namespace Umbraco.Core.Services /// /// Sorts documents. /// - OperationResult Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + OperationResult Sort(IEnumerable items, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// /// Sorts documents. /// - OperationResult Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true); + OperationResult Sort(IEnumerable ids, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); #endregion @@ -336,29 +336,30 @@ namespace Umbraco.Core.Services /// /// /// By default, publishes all variations of the document, but it is possible to specify a culture to be published. - /// When a culture is being published, it includes all varying values along with all invariant values. For - /// anything more complicated, see . + /// When a culture is being published, it includes all varying values along with all invariant values. /// The document is *always* saved, even when publishing fails. /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. /// - PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = 0, bool raiseEvents = true); + /// The document to publish. + /// The culture to publish. + /// The identifier of the user performing the action. + /// A value indicating whether to raise events. + PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// - /// Saves and publishes a publishing document. + /// Saves and publishes a document. /// /// - /// A publishing document is a document with values that are being published, i.e. - /// that have been published or cleared via and - /// . - /// When one needs to publish or unpublish a single culture, or all cultures, using - /// and is the way to go. But if one needs to, say, publish two cultures and unpublish a third - /// one, in one go, then one needs to invoke and - /// on the content itself - this prepares the content, but does not commit anything - and then, invoke - /// to actually commit the changes to the database. + /// By default, publishes all variations of the document, but it is possible to specify a culture to be published. + /// When a culture is being published, it includes all varying values along with all invariant values. /// The document is *always* saved, even when publishing fails. /// - PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true); + /// The document to publish. + /// The cultures to publish. + /// The identifier of the user performing the action. + /// A value indicating whether to raise events. + PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// /// Saves and publishes a document branch. @@ -374,7 +375,7 @@ namespace Umbraco.Core.Services /// only those documents that are already published, are republished. When true, all documents are /// published. The root of the branch is always published, regardless of . /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = 0); + IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId); /// /// Saves and publishes a document branch. @@ -388,32 +389,32 @@ namespace Umbraco.Core.Services /// only those documents that are already published, are republished. When true, all documents are /// published. The root of the branch is always published, regardless of . /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = 0); + IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId); - /// - /// Saves and publishes a document branch. - /// - /// The root document. - /// A value indicating whether to force-publish documents that are not already published. - /// A function determining cultures to publish. - /// A function publishing cultures. - /// The identifier of the user performing the operation. - /// - /// The parameter determines which documents are published. When false, - /// only those documents that are already published, are republished. When true, all documents are - /// published. The root of the branch is always published, regardless of . - /// The parameter is a function which determines whether a document has - /// changes to publish (else there is no need to publish it). If one wants to publish only a selection of - /// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other - /// cultures may trigger an unwanted republish. - /// The parameter is a function to execute to publish cultures, on - /// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating - /// whether the cultures could be published. - /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, - Func> shouldPublish, - Func, bool> publishCultures, - int userId = 0); + ///// + ///// Saves and publishes a document branch. + ///// + ///// The root document. + ///// A value indicating whether to force-publish documents that are not already published. + ///// A function determining cultures to publish. + ///// A function publishing cultures. + ///// The identifier of the user performing the operation. + ///// + ///// The parameter determines which documents are published. When false, + ///// only those documents that are already published, are republished. When true, all documents are + ///// published. The root of the branch is always published, regardless of . + ///// The parameter is a function which determines whether a document has + ///// changes to publish (else there is no need to publish it). If one wants to publish only a selection of + ///// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other + ///// cultures may trigger an unwanted republish. + ///// The parameter is a function to execute to publish cultures, on + ///// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating + ///// whether the cultures could be published. + ///// + //IEnumerable SaveAndPublishBranch(IContent content, bool force, + // Func> shouldPublish, + // Func, bool> publishCultures, + // int userId = Constants.Security.SuperUserId); /// /// Unpublishes a document. @@ -425,7 +426,7 @@ namespace Umbraco.Core.Services /// If the content type is variant, then culture can be either '*' or an actual culture, but neither null nor /// empty. If the content type is invariant, then culture can be either '*' or null or empty. /// - PublishResult Unpublish(IContent content, string culture = "*", int userId = 0); + PublishResult Unpublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId); /// /// Gets a value indicating whether a document is path-publishable. @@ -442,7 +443,7 @@ namespace Umbraco.Core.Services /// /// Saves a document and raises the "sent to publication" events. /// - bool SendToPublication(IContent content, int userId = 0); + bool SendToPublication(IContent content, int userId = Constants.Security.SuperUserId); /// /// Publishes and unpublishes scheduled documents. @@ -477,27 +478,27 @@ namespace Umbraco.Core.Services /// /// Creates a document. /// - IContent Create(string name, Guid parentId, string documentTypeAlias, int userId = 0); + IContent Create(string name, Guid parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates a document. /// - IContent Create(string name, int parentId, string documentTypeAlias, int userId = 0); + IContent Create(string name, int parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates a document. /// - IContent Create(string name, IContent parent, string documentTypeAlias, int userId = 0); + IContent Create(string name, IContent parent, string documentTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates and saves a document. /// - IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = 0); + IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates and saves a document. /// - IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); + IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = Constants.Security.SuperUserId); #endregion @@ -513,7 +514,7 @@ namespace Umbraco.Core.Services /// /// When no culture is specified, all cultures are rolled back. /// - OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0); + OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId); #endregion } diff --git a/src/Umbraco.Core/Services/IContentTypeBaseServiceProvider.cs b/src/Umbraco.Core/Services/IContentTypeBaseServiceProvider.cs new file mode 100644 index 0000000000..d0146ce043 --- /dev/null +++ b/src/Umbraco.Core/Services/IContentTypeBaseServiceProvider.cs @@ -0,0 +1,27 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// Provides the corresponding to an object. + /// + public interface IContentTypeBaseServiceProvider + { + /// + /// Gets the content type service base managing types for the specified content base. + /// + /// + /// If is an , this returns the + /// , and if it's an , this returns + /// the , etc. + /// Services are returned as and can be used + /// to retrieve the content / media / whatever type as . + /// + IContentTypeBaseService For(IContentBase contentBase); + + /// + /// Gets the content type of an object. + /// + IContentTypeComposition GetContentTypeOf(IContentBase contentBase); + } +} diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index bddd276e58..02e4bc6b18 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Services /// /// Manages objects. /// - public interface IContentTypeService : IContentTypeServiceBase + public interface IContentTypeService : IContentTypeBaseService { /// /// Gets all property type aliases. diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index bc091535d3..b86494adb5 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -4,15 +4,37 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Services { + /// + /// Provides a common base interface for . + /// + public interface IContentTypeBaseService + { + /// + /// Gets a content type. + /// + IContentTypeComposition Get(int id); + } + /// /// Provides a common base interface for , and . /// /// The type of the item. - public interface IContentTypeServiceBase : IService + public interface IContentTypeBaseService : IContentTypeBaseService, IService where TItem : IContentTypeComposition { + /// + /// Gets a content type. + /// TItem Get(int id); + + /// + /// Gets a content type. + /// TItem Get(Guid key); + + /// + /// Gets a content type. + /// TItem Get(string alias); int Count(); @@ -25,11 +47,11 @@ namespace Umbraco.Core.Services IEnumerable GetChildren(int id); bool HasChildren(int id); - void Save(TItem item, int userId = 0); - void Save(IEnumerable items, int userId = 0); + void Save(TItem item, int userId = Constants.Security.SuperUserId); + void Save(IEnumerable items, int userId = Constants.Security.SuperUserId); - void Delete(TItem item, int userId = 0); - void Delete(IEnumerable item, int userId = 0); + void Delete(TItem item, int userId = Constants.Security.SuperUserId); + void Delete(IEnumerable item, int userId = Constants.Security.SuperUserId); Attempt ValidateComposition(TItem compo); @@ -41,15 +63,15 @@ namespace Umbraco.Core.Services /// bool HasContainerInPath(string contentPath); - Attempt> CreateContainer(int parentContainerId, string name, int userId = 0); - Attempt SaveContainer(EntityContainer container, int userId = 0); + Attempt> CreateContainer(int parentContainerId, string name, int userId = Constants.Security.SuperUserId); + Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId); EntityContainer GetContainer(int containerId); EntityContainer GetContainer(Guid containerId); IEnumerable GetContainers(int[] containerIds); IEnumerable GetContainers(TItem contentType); IEnumerable GetContainers(string folderName, int level); - Attempt DeleteContainer(int containerId, int userId = 0); - Attempt> RenameContainer(int id, string name, int userId = 0); + Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId); + Attempt> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId); Attempt> Move(TItem moving, int containerId); Attempt> Copy(TItem copying, int containerId); diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 537c0e629f..3dc530e250 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -9,15 +9,15 @@ namespace Umbraco.Core.Services /// public interface IDataTypeService : IService { - Attempt> CreateContainer(int parentId, string name, int userId = 0); - Attempt SaveContainer(EntityContainer container, int userId = 0); + Attempt> CreateContainer(int parentId, string name, int userId = Constants.Security.SuperUserId); + Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId); EntityContainer GetContainer(int containerId); EntityContainer GetContainer(Guid containerId); IEnumerable GetContainers(string folderName, int level); IEnumerable GetContainers(IDataType dataType); IEnumerable GetContainers(int[] containerIds); - Attempt DeleteContainer(int containerId, int userId = 0); - Attempt> RenameContainer(int id, string name, int userId = 0); + Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId); + Attempt> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId); /// /// Gets a by its Name @@ -52,14 +52,14 @@ namespace Umbraco.Core.Services /// /// to save /// Id of the user issuing the save - void Save(IDataType dataType, int userId = 0); + void Save(IDataType dataType, int userId = Constants.Security.SuperUserId); /// /// Saves a collection of /// /// to save /// Id of the user issuing the save - void Save(IEnumerable dataTypeDefinitions, int userId = 0); + void Save(IEnumerable dataTypeDefinitions, int userId = Constants.Security.SuperUserId); /// /// Saves a collection of @@ -78,7 +78,7 @@ namespace Umbraco.Core.Services /// /// to delete /// Id of the user issuing the deletion - void Delete(IDataType dataType, int userId = 0); + void Delete(IDataType dataType, int userId = Constants.Security.SuperUserId); /// /// Gets a by its control Id diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 8596424b03..5fe52559ee 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -18,12 +18,12 @@ namespace Umbraco.Core.Services IPartialView GetPartialView(string path); IPartialView GetPartialViewMacro(string path); IEnumerable GetPartialViewMacros(params string[] names); - Attempt CreatePartialView(IPartialView partialView, string snippetName = null, int userId = 0); - Attempt CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = 0); - bool DeletePartialView(string path, int userId = 0); - bool DeletePartialViewMacro(string path, int userId = 0); - Attempt SavePartialView(IPartialView partialView, int userId = 0); - Attempt SavePartialViewMacro(IPartialView partialView, int userId = 0); + Attempt CreatePartialView(IPartialView partialView, string snippetName = null, int userId = Constants.Security.SuperUserId); + Attempt CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = Constants.Security.SuperUserId); + bool DeletePartialView(string path, int userId = Constants.Security.SuperUserId); + bool DeletePartialViewMacro(string path, int userId = Constants.Security.SuperUserId); + Attempt SavePartialView(IPartialView partialView, int userId = Constants.Security.SuperUserId); + Attempt SavePartialViewMacro(IPartialView partialView, int userId = Constants.Security.SuperUserId); bool ValidatePartialView(PartialView partialView); bool ValidatePartialViewMacro(PartialView partialView); @@ -45,14 +45,14 @@ namespace Umbraco.Core.Services /// /// to save /// Optional id of the user saving the stylesheet - void SaveStylesheet(Stylesheet stylesheet, int userId = 0); + void SaveStylesheet(Stylesheet stylesheet, int userId = Constants.Security.SuperUserId); /// /// Deletes a stylesheet by its name /// /// Name incl. extension of the Stylesheet to delete /// Optional id of the user deleting the stylesheet - void DeleteStylesheet(string path, int userId = 0); + void DeleteStylesheet(string path, int userId = Constants.Security.SuperUserId); /// /// Validates a @@ -79,14 +79,14 @@ namespace Umbraco.Core.Services /// /// to save /// Optional id of the user saving the script - void SaveScript(Script script, int userId = 0); + void SaveScript(Script script, int userId = Constants.Security.SuperUserId); /// /// Deletes a script by its name /// /// Name incl. extension of the Script to delete /// Optional id of the user deleting the script - void DeleteScript(string path, int userId = 0); + void DeleteScript(string path, int userId = Constants.Security.SuperUserId); /// /// Validates a @@ -187,7 +187,7 @@ namespace Umbraco.Core.Services /// /// to save /// Optional id of the user saving the template - void SaveTemplate(ITemplate template, int userId = 0); + void SaveTemplate(ITemplate template, int userId = Constants.Security.SuperUserId); /// /// Creates a template for a content type @@ -198,16 +198,16 @@ namespace Umbraco.Core.Services /// /// The template created /// - Attempt> CreateTemplateForContentType(string contentTypeAlias, string contentTypeName, int userId = 0); + Attempt> CreateTemplateForContentType(string contentTypeAlias, string contentTypeName, int userId = Constants.Security.SuperUserId); - ITemplate CreateTemplateWithIdentity(string name, string alias, string content, ITemplate masterTemplate = null, int userId = 0); + ITemplate CreateTemplateWithIdentity(string name, string alias, string content, ITemplate masterTemplate = null, int userId = Constants.Security.SuperUserId); /// /// Deletes a template by its alias /// /// Alias of the to delete /// Optional id of the user deleting the template - void DeleteTemplate(string alias, int userId = 0); + void DeleteTemplate(string alias, int userId = Constants.Security.SuperUserId); /// /// Validates a @@ -221,7 +221,7 @@ namespace Umbraco.Core.Services /// /// List of to save /// Optional id of the user - void SaveTemplate(IEnumerable templates, int userId = 0); + void SaveTemplate(IEnumerable templates, int userId = Constants.Security.SuperUserId); /// /// Gets the content of a template as a stream. diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs index 4882c7e829..54022d37bb 100644 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ b/src/Umbraco.Core/Services/ILocalizationService.cs @@ -86,7 +86,7 @@ namespace Umbraco.Core.Services /// /// to save /// Optional id of the user saving the dictionary item - void Save(IDictionaryItem dictionaryItem, int userId = 0); + void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); /// /// Deletes a object and its related translations @@ -94,7 +94,7 @@ namespace Umbraco.Core.Services /// /// to delete /// Optional id of the user deleting the dictionary item - void Delete(IDictionaryItem dictionaryItem, int userId = 0); + void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); /// /// Gets a by its id @@ -153,14 +153,14 @@ namespace Umbraco.Core.Services /// /// to save /// Optional id of the user saving the language - void Save(ILanguage language, int userId = 0); + void Save(ILanguage language, int userId = Constants.Security.SuperUserId); /// /// Deletes a by removing it and its usages from the db /// /// to delete /// Optional id of the user deleting the language - void Delete(ILanguage language, int userId = 0); + void Delete(ILanguage language, int userId = Constants.Security.SuperUserId); /// /// Gets the full dictionary key map. diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs index 8c3acfd619..597c986f37 100644 --- a/src/Umbraco.Core/Services/IMacroService.cs +++ b/src/Umbraco.Core/Services/IMacroService.cs @@ -39,14 +39,14 @@ namespace Umbraco.Core.Services /// /// to delete /// Optional id of the user deleting the macro - void Delete(IMacro macro, int userId = 0); + void Delete(IMacro macro, int userId = Constants.Security.SuperUserId); /// /// Saves an /// /// to save /// Optional id of the user saving the macro - void Save(IMacro macro, int userId = 0); + void Save(IMacro macro, int userId = Constants.Security.SuperUserId); ///// ///// Gets a list all available plugins diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index ce2062d08d..78da440bc6 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Services /// Alias of the /// Optional id of the user creating the media item /// - IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = 0); + IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates an object using the alias of the @@ -53,7 +53,7 @@ namespace Umbraco.Core.Services /// Alias of the /// Optional id of the user creating the media item /// - IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0); + IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates an object using the alias of the @@ -69,7 +69,7 @@ namespace Umbraco.Core.Services /// Alias of the /// Optional id of the user creating the media item /// - IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0); + IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Gets an object by Id @@ -100,9 +100,7 @@ namespace Umbraco.Core.Services /// Page number /// Page size /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field + /// /// /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, @@ -152,14 +150,14 @@ namespace Umbraco.Core.Services /// Id of the Media's new Parent /// Id of the User moving the Media /// True if moving succeeded, otherwise False - Attempt Move(IMedia media, int parentId, int userId = 0); + Attempt Move(IMedia media, int parentId, int userId = Constants.Security.SuperUserId); /// /// Deletes an object by moving it to the Recycle Bin /// /// The to delete /// Id of the User deleting the Media - Attempt MoveToRecycleBin(IMedia media, int userId = 0); + Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId); /// /// Empties the Recycle Bin by deleting all that resides in the bin @@ -172,7 +170,7 @@ namespace Umbraco.Core.Services /// This needs extra care and attention as its potentially a dangerous and extensive operation /// Id of the /// Optional Id of the user deleting Media - void DeleteMediaOfType(int mediaTypeId, int userId = 0); + void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId); /// /// Deletes all media of the specified types. All Descendants of deleted media that is not of these types is moved to Recycle Bin. @@ -180,7 +178,7 @@ namespace Umbraco.Core.Services /// This needs extra care and attention as its potentially a dangerous and extensive operation /// Ids of the s /// Optional Id of the user issuing the delete operation - void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = 0); + void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Constants.Security.SuperUserId); /// /// Permanently deletes an object @@ -191,7 +189,7 @@ namespace Umbraco.Core.Services /// /// The to delete /// Id of the User deleting the Media - Attempt Delete(IMedia media, int userId = 0); + Attempt Delete(IMedia media, int userId = Constants.Security.SuperUserId); /// /// Saves a single object @@ -199,7 +197,7 @@ namespace Umbraco.Core.Services /// The to save /// Id of the User saving the Media /// Optional boolean indicating whether or not to raise events. - Attempt Save(IMedia media, int userId = 0, bool raiseEvents = true); + Attempt Save(IMedia media, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// /// Saves a collection of objects @@ -207,7 +205,7 @@ namespace Umbraco.Core.Services /// Collection of to save /// Id of the User saving the Media /// Optional boolean indicating whether or not to raise events. - Attempt Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); + Attempt Save(IEnumerable medias, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// /// Gets an object by its 'UniqueId' @@ -250,7 +248,7 @@ namespace Umbraco.Core.Services /// Id of the object to delete versions from /// Latest version date /// Optional Id of the User deleting versions of a Content object - void DeleteVersions(int id, DateTime versionDate, int userId = 0); + void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId); /// /// Permanently deletes specific version(s) from an object. @@ -259,7 +257,7 @@ namespace Umbraco.Core.Services /// Id of the version to delete /// Boolean indicating whether to delete versions prior to the versionId /// Optional Id of the User deleting versions of a Content object - void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = 0); + void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId); /// /// Gets an object from the path stored in the 'umbracoFile' property. @@ -304,7 +302,7 @@ namespace Umbraco.Core.Services /// /// /// True if sorting succeeded, otherwise False - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + bool Sort(IEnumerable items, int userId = Constants.Security.SuperUserId, bool raiseEvents = true); /// /// Creates an object using the alias of the @@ -319,7 +317,7 @@ namespace Umbraco.Core.Services /// Alias of the /// Optional id of the user creating the media item /// - IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0); + IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates an object using the alias of the @@ -334,7 +332,7 @@ namespace Umbraco.Core.Services /// Alias of the /// Optional id of the user creating the media item /// - IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0); + IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Gets the content of a media as a stream. diff --git a/src/Umbraco.Core/Services/IMediaTypeService.cs b/src/Umbraco.Core/Services/IMediaTypeService.cs index b125f4ca13..992937675f 100644 --- a/src/Umbraco.Core/Services/IMediaTypeService.cs +++ b/src/Umbraco.Core/Services/IMediaTypeService.cs @@ -5,6 +5,6 @@ namespace Umbraco.Core.Services /// /// Manages objects. /// - public interface IMediaTypeService : IContentTypeServiceBase + public interface IMediaTypeService : IContentTypeBaseService { } } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 8a9d11f766..ee0e2ef5ed 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -99,7 +99,6 @@ namespace Umbraco.Core.Services /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method which can be /// used during Member creation. /// - /// /// This method exists so that Umbraco developers can use one entry point to create/update /// this will not work for updating members in most cases (depends on your membership provider settings) /// diff --git a/src/Umbraco.Core/Services/IMemberTypeService.cs b/src/Umbraco.Core/Services/IMemberTypeService.cs index e6dfef6998..41004d267d 100644 --- a/src/Umbraco.Core/Services/IMemberTypeService.cs +++ b/src/Umbraco.Core/Services/IMemberTypeService.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Services /// /// Manages objects. /// - public interface IMemberTypeService : IContentTypeServiceBase + public interface IMemberTypeService : IContentTypeBaseService { string GetDefault(); } diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index c14882fe7a..b38b5a426b 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Services /// /// /// - IEnumerable InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0); + IEnumerable InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = Constants.Security.SuperUserId); /// /// Installs the data, entities, objects contained in an umbraco package file (zip) @@ -35,7 +35,7 @@ namespace Umbraco.Core.Services /// /// /// - InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0); + InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = Constants.Security.SuperUserId); /// /// Uninstalls all versions of the package by name @@ -43,7 +43,7 @@ namespace Umbraco.Core.Services /// /// /// - UninstallationSummary UninstallPackage(string packageName, int userId = 0); + UninstallationSummary UninstallPackage(string packageName, int userId = Constants.Security.SuperUserId); #endregion @@ -75,7 +75,7 @@ namespace Umbraco.Core.Services /// If the package is an upgrade, the original/current PackageDefinition is returned /// PackageInstallType GetPackageInstallType(string packageName, SemVersion packageVersion, out PackageDefinition alreadyInstalled); - void DeleteInstalledPackage(int packageId, int userId = 0); + void DeleteInstalledPackage(int packageId, int userId = Constants.Security.SuperUserId); /// /// Persists a package definition to storage @@ -89,7 +89,7 @@ namespace Umbraco.Core.Services IEnumerable GetAllCreatedPackages(); PackageDefinition GetCreatedPackageById(int id); - void DeleteCreatedPackage(int id, int userId = 0); + void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId); /// /// Persists a package definition to storage diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index 9ec62fda0f..f352def49e 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -158,7 +158,7 @@ namespace Umbraco.Core.Services // multiple times, but we don't lock the cache while accessing the database = better int? val = null; - + if (_dictionary.TryGetValue(umbracoObjectType, out var mappers)) if ((val = mappers.key2id(key)) == default(int)) val = null; diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 266f34cc37..cc962fbe22 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -4,13 +4,13 @@ using System.Globalization; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services.Changes; @@ -27,18 +27,18 @@ namespace Umbraco.Core.Services.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly IDocumentBlueprintRepository _documentBlueprintRepository; private readonly ILanguageRepository _languageRepository; - private readonly IMediaFileSystem _mediaFileSystem; private IQuery _queryNotTrashed; + //TODO: The non-lazy object should be injected + private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService()); #region Constructors public ContentService(IScopeProvider provider, ILogger logger, - IEventMessagesFactory eventMessagesFactory, IMediaFileSystem mediaFileSystem, + IEventMessagesFactory eventMessagesFactory, IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository) : base(provider, logger, eventMessagesFactory) { - _mediaFileSystem = mediaFileSystem; _documentRepository = documentRepository; _entityRepository = entityRepository; _auditRepository = auditRepository; @@ -162,7 +162,7 @@ namespace Umbraco.Core.Services.Implement /// Alias of the /// Optional id of the user creating the content /// - public IContent Create(string name, Guid parentId, string contentTypeAlias, int userId = 0) + public IContent Create(string name, Guid parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? @@ -182,7 +182,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent Create(string name, int parentId, string contentTypeAlias, int userId = 0) + public IContent Create(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? @@ -196,7 +196,7 @@ namespace Umbraco.Core.Services.Implement var content = new Content(name, parentId, contentType); using (var scope = ScopeProvider.CreateScope()) { - CreateContent(scope, content, parent, userId, false); + CreateContent(scope, content, userId, false); scope.Complete(); } @@ -215,7 +215,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent Create(string name, IContent parent, string contentTypeAlias, int userId = 0) + public IContent Create(string name, IContent parent, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? @@ -230,7 +230,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback var content = new Content(name, parent, contentType); - CreateContent(scope, content, parent, userId, false); + CreateContent(scope, content, userId, false); scope.Complete(); return content; @@ -246,7 +246,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = 0) + public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? @@ -264,7 +264,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentException("No content with that id.", nameof(parentId)); // causes rollback var content = parentId > 0 ? new Content(name, parent, contentType) : new Content(name, parentId, contentType); - CreateContent(scope, content, parent, userId, true); + CreateContent(scope, content, userId, true); scope.Complete(); return content; @@ -280,7 +280,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0) + public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? @@ -296,34 +296,33 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback var content = new Content(name, parent, contentType); - CreateContent(scope, content, parent, userId, true); + CreateContent(scope, content, userId, true); scope.Complete(); return content; } } - private void CreateContent(IScope scope, Content content, IContent parent, int userId, bool withIdentity) + private void CreateContent(IScope scope, IContent content, int userId, bool withIdentity) { content.CreatorId = userId; content.WriterId = userId; if (withIdentity) { + var evtMsgs = EventMessagesFactory.Get(); + // if saving is cancelled, content remains without an identity - var saveEventArgs = new SaveEventArgs(content); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) return; _documentRepository.Save(content); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshNode).ToEventArgs()); } - scope.Events.Dispatch(Created, this, new NewEventArgs(content, false, content.ContentType.Alias, parent)); - if (withIdentity == false) return; @@ -751,7 +750,7 @@ namespace Umbraco.Core.Services.Implement #region Save, Publish, Unpublish /// - public OperationResult Save(IContent content, int userId = 0, bool raiseEvents = true) + public OperationResult Save(IContent content, int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) @@ -761,8 +760,8 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); + if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -776,7 +775,7 @@ namespace Umbraco.Core.Services.Implement //track the cultures that have changed var culturesChanging = content.ContentType.VariesByCulture() - ? content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key).ToList() + ? content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; // TODO: Currently there's no way to change track which variant properties have changed, we only have change // tracking enabled on all values on the Property which doesn't allow us to know which variants have changed. @@ -787,8 +786,7 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); } var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); @@ -810,15 +808,15 @@ namespace Umbraco.Core.Services.Implement } /// - public OperationResult Save(IEnumerable contents, int userId = 0, bool raiseEvents = true) + public OperationResult Save(IEnumerable contents, int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); var contentsA = contents.ToArray(); using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(contentsA, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + var saveEventArgs = new ContentSavingEventArgs(contentsA, evtMsgs); + if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -838,8 +836,7 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content"); @@ -851,13 +848,13 @@ namespace Umbraco.Core.Services.Implement } /// - public PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = 0, bool raiseEvents = true) + public PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); var publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) - throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(SavePublishing)} method."); + throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method."); // cannot accept invariant (null or empty) culture for variant content type // cannot accept a specific culture for invariant content type (but '*' is ok) @@ -872,38 +869,107 @@ namespace Umbraco.Core.Services.Implement throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); } - // if culture is specific, first publish the invariant values, then publish the culture itself. - // if culture is '*', then publish them all (including variants) - - // explicitly SaveAndPublish a specific culture also publishes invariant values - if (!culture.IsNullOrWhiteSpace() && culture != "*") + using (var scope = ScopeProvider.CreateScope()) { - // publish the invariant values - var publishInvariant = content.PublishCulture(null); - if (!publishInvariant) + scope.WriteLock(Constants.Locks.ContentTree); + + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); + if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + + Property[] invalidProperties; + + // if culture is specific, first publish the invariant values, then publish the culture itself. + // if culture is '*', then publish them all (including variants) + + // explicitly SaveAndPublish a specific culture also publishes invariant values + if (!culture.IsNullOrWhiteSpace() && culture != "*") + { + // publish the invariant values + var publishInvariant = content.PublishCulture(null); + if (!publishInvariant) + return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); + + //validate the property values + if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties)) + return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) + { + InvalidProperties = invalidProperties + }; + } + + // publish the culture(s) + var publishCulture = content.PublishCulture(culture); + if (!publishCulture) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); + + //validate the property values + if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties)) + return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) + { + InvalidProperties = invalidProperties + }; + + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); + scope.Complete(); + return result; } - - // publish the culture(s) - var publishCulture = content.PublishCulture(culture); - if (!publishCulture) - return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); - - // finally, "save publishing" - // what happens next depends on whether the content can be published or not - return SavePublishing(content, userId, raiseEvents); } /// - public PublishResult Unpublish(IContent content, string culture = "*", int userId = 0) + public PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = 0, bool raiseEvents = true) { + if (content == null) throw new ArgumentNullException(nameof(content)); + if (cultures == null) throw new ArgumentNullException(nameof(cultures)); + + using (var scope = ScopeProvider.CreateScope()) + { + scope.WriteLock(Constants.Locks.ContentTree); + + var evtMsgs = EventMessagesFactory.Get(); + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); + if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + + var varies = content.ContentType.VariesByCulture(); + + if (cultures.Length == 0) + { + //no cultures specified and doesn't vary, so publish it, else nothing to publish + return !varies + ? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents) + : new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); + } + + + if (cultures.Select(content.PublishCulture).Any(isValid => !isValid)) + return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); + + //validate the property values + if (!_propertyValidationService.Value.IsPropertyDataValid(content, out var invalidProperties)) + return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) + { + InvalidProperties = invalidProperties + }; + + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); + scope.Complete(); + return result; + } + } + + /// + public PublishResult Unpublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + var evtMsgs = EventMessagesFactory.Get(); culture = culture.NullOrWhiteSpaceAsNull(); var publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) - throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(SavePublishing)} method."); + throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method."); // cannot accept invariant (null or empty) culture for variant content type // cannot accept a specific culture for invariant content type (but '*' is ok) @@ -922,46 +988,93 @@ namespace Umbraco.Core.Services.Implement if (!content.Published) return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); - // all cultures = unpublish whole - if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null)) - { - ((Content)content).PublishedState = PublishedState.Unpublishing; - } - else - { - // if the culture we want to unpublish was already unpublished, nothing to do - if (!content.WasCulturePublished(culture)) - return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); - - // unpublish the culture - content.UnpublishCulture(culture); - } - - // finally, "save publishing" - return SavePublishing(content, userId); - } - - /// - public PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true) - { using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); - var result = SavePublishingInternal(scope, content, userId, raiseEvents); + + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + + // all cultures = unpublish whole + if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null)) + { + content.PublishedState = PublishedState.Unpublishing; + } + else + { + // If the culture we want to unpublish was already unpublished, nothing to do. + // To check for that we need to lookup the persisted content item + var persisted = content.HasIdentity ? GetById(content.Id) : null; + + if (persisted != null && !persisted.IsCulturePublished(culture)) + return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); + + // unpublish the culture + content.UnpublishCulture(culture); + } + + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId); + scope.Complete(); + return result; + } + + } + + /// + /// Saves a document and publishes/unpublishes any pending publishing changes made to the document. + /// + /// + /// + /// This MUST NOT be called from within this service, this used to be a public API and must only be used outside of this service. + /// Internally in this service, calls must be made to CommitDocumentChangesInternal + /// + /// + /// This is the underlying logic for both publishing and unpublishing any document + /// Pending publishing/unpublishing changes on a document are made with calls to and + /// . + /// When publishing or unpublishing a single culture, or all cultures, use + /// and . But if the flexibility to both publish and unpublish in a single operation is required + /// then this method needs to be used in combination with and + /// on the content itself - this prepares the content, but does not commit anything - and then, invoke + /// to actually commit the changes to the database. + /// The document is *always* saved, even when publishing fails. + /// + internal PublishResult CommitDocumentChanges(IContent content, + int userId = Constants.Security.SuperUserId, bool raiseEvents = true) + { + using (var scope = ScopeProvider.CreateScope()) + { + var evtMsgs = EventMessagesFactory.Get(); + + scope.WriteLock(Constants.Locks.ContentTree); + + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); + if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); scope.Complete(); return result; } } - private PublishResult SavePublishingInternal(IScope scope, IContent content, int userId = 0, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false) + private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, + ContentSavingEventArgs saveEventArgs, + int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false) { - var evtMsgs = EventMessagesFactory.Get(); + if (scope == null) throw new ArgumentNullException(nameof(scope)); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (saveEventArgs == null) throw new ArgumentNullException(nameof(saveEventArgs)); + + var evtMsgs = saveEventArgs.Messages; + PublishResult publishResult = null; PublishResult unpublishResult = null; // nothing set = republish it all if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing) - ((Content)content).PublishedState = PublishedState.Publishing; + content.PublishedState = PublishedState.Publishing; // state here is either Publishing or Unpublishing // (even though, Publishing to unpublish a culture may end up unpublishing everything) @@ -974,31 +1087,26 @@ namespace Umbraco.Core.Services.Implement IReadOnlyList culturesPublishing = null; IReadOnlyList culturesUnpublishing = null; IReadOnlyList culturesChanging = variesByCulture - ? content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key).ToList() + ? content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; var isNew = !content.HasIdentity; var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch; var previouslyPublished = content.HasIdentity && content.Published; - // always save - var saveEventArgs = new SaveEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); - if (publishing) { culturesUnpublishing = content.GetCulturesUnpublishing(); culturesPublishing = variesByCulture - ? content.PublishCultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key).ToList() + ? content.PublishCultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; // ensure that the document can be published, and publish handling events, business rules, etc - publishResult = StrategyCanPublish(scope, content, userId, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs); + publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! - publishResult = StrategyPublish(scope, content, userId, culturesPublishing, culturesUnpublishing, evtMsgs); + publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs); } else { @@ -1017,8 +1125,11 @@ namespace Umbraco.Core.Services.Implement } // reset published state from temp values (publishing, unpublishing) to original value - // (published, unpublished) in order to save the document, unchanged - ((Content)content).Published = content.Published; + // (published, unpublished) in order to save the document, unchanged - yes, this is odd, + // but: (a) it means we don't reproduce the PublishState logic here and (b) setting the + // PublishState to anything other than Publishing or Unpublishing - which is precisely + // what we want to do here - throws + content.Published = content.Published; } } @@ -1034,14 +1145,17 @@ namespace Umbraco.Core.Services.Implement // handling events, business rules, etc // note: StrategyUnpublish flips the PublishedState to Unpublishing! // note: This unpublishes the entire document (not different variants) - unpublishResult = StrategyCanUnpublish(scope, content, userId, evtMsgs); + unpublishResult = StrategyCanUnpublish(scope, content, evtMsgs); if (unpublishResult.Success) unpublishResult = StrategyUnpublish(scope, content, userId, evtMsgs); else { // reset published state from temp values (publishing, unpublishing) to original value - // (published, unpublished) in order to save the document, unchanged - ((Content)content).Published = content.Published; + // (published, unpublished) in order to save the document, unchanged - yes, this is odd, + // but: (a) it means we don't reproduce the PublishState logic here and (b) setting the + // PublishState to anything other than Publishing or Unpublishing - which is precisely + // what we want to do here - throws + content.Published = content.Published; } } else @@ -1064,8 +1178,7 @@ namespace Umbraco.Core.Services.Implement // raise the Saved event, always if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); } if (unpublishing) // we have tried to unpublish - won't happen in a branch @@ -1108,16 +1221,16 @@ namespace Umbraco.Core.Services.Implement if (!branchOne) // for branches, handled by SaveAndPublishBranch { scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - scope.Events.Dispatch(Published, this, new PublishEventArgs(content, false, false), "Published"); + scope.Events.Dispatch(Published, this, saveEventArgs.ToContentPublishedEventArgs(), nameof(Published)); } - // if was not published and now is... descendants that were 'published' (but + // it was not published and now is... descendants that were 'published' (but // had an unpublished ancestor) are 're-published' ie not explicitly published // but back as 'published' nevertheless if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id)) { var descendants = GetPublishedDescendantsLocked(content).ToArray(); - scope.Events.Dispatch(Published, this, new PublishEventArgs(descendants, false, false), "Published"); + scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(descendants, false, evtMsgs), "Published"); } switch (publishResult.Result) @@ -1200,6 +1313,13 @@ namespace Umbraco.Core.Services.Implement .Distinct() .ToList(); + if (pendingCultures.Count == 0) + break; //shouldn't happen but no point in continuing if there's nothing there + + var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d); + var publishing = true; foreach (var culture in pendingCultures) { @@ -1208,7 +1328,14 @@ namespace Umbraco.Core.Services.Implement if (d.Trashed) continue; // won't publish - publishing &= d.PublishCulture(culture); //set the culture to be published + //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed + Property[] invalidProperties = null; + var tryPublish = d.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties); + if (invalidProperties != null && invalidProperties.Length > 0) + Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", + d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); + + publishing &= tryPublish; //set the culture to be published if (!publishing) break; // no point continuing } @@ -1217,7 +1344,8 @@ namespace Umbraco.Core.Services.Implement else if (!publishing) result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d); else - result = SavePublishing(d, d.WriterId); + result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId); + if (result.Success == false) Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); @@ -1251,6 +1379,13 @@ namespace Umbraco.Core.Services.Implement .Distinct() .ToList(); + if (pendingCultures.Count == 0) + break; //shouldn't happen but no point in continuing if there's nothing there + + var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d); + foreach (var c in pendingCultures) { //Clear this schedule for this culture @@ -1259,13 +1394,11 @@ namespace Umbraco.Core.Services.Implement d.UnpublishCulture(c); } - if (pendingCultures.Count > 0) - { - result = SavePublishing(d, d.WriterId); - if (result.Success == false) - Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; - } + result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId); + if (result.Success == false) + Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + yield return result; + } else { @@ -1286,16 +1419,20 @@ namespace Umbraco.Core.Services.Implement } } - private bool SaveAndPublishBranch_PublishCultures(IContent c, HashSet culturesToPublish) + // utility 'PublishCultures' func used by SaveAndPublishBranch + private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish) { + //TODO: This does not support being able to return invalid property details to bubble up to the UI + // variant content type - publish specified cultures // invariant content type - publish only the invariant culture - return c.ContentType.VariesByCulture() - ? culturesToPublish.All(c.PublishCulture) - : c.PublishCulture(); + return content.ContentType.VariesByCulture() + ? culturesToPublish.All(culture => content.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(content, out _)) + : content.PublishCulture() && _propertyValidationService.Value.IsPropertyDataValid(content, out _); } - private HashSet SaveAndPublishBranch_ShouldPublish3(ref HashSet cultures, string c, bool published, bool edited, bool isRoot, bool force) + // utility 'ShouldPublish' func used by SaveAndPublishBranch + private HashSet SaveAndPublishBranch_ShouldPublish(ref HashSet cultures, string c, bool published, bool edited, bool isRoot, bool force) { // if published, republish if (published) @@ -1313,9 +1450,8 @@ namespace Umbraco.Core.Services.Implement return cultures; } - /// - public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = 0) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId) { // note: EditedValue and PublishedValue are objects here, so it is important to .Equals() // and not to == them, else we would be comparing references, and that is a bad thing @@ -1332,10 +1468,10 @@ namespace Umbraco.Core.Services.Implement HashSet culturesToPublish = null; if (!c.ContentType.VariesByCulture()) // invariant content type - return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); if (culture != "*") // variant content type, specific culture - return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force); + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force); // variant content type, all cultures if (c.Published) @@ -1343,7 +1479,7 @@ namespace Umbraco.Core.Services.Implement // then some (and maybe all) cultures will be 'already published' (unless forcing), // others will have to 'republish this culture' foreach (var x in c.AvailableCultures) - SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); + SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); return culturesToPublish; } @@ -1357,7 +1493,7 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = 0) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId) { // note: EditedValue and PublishedValue are objects here, so it is important to .Equals() // and not to == them, else we would be comparing references, and that is a bad thing @@ -1372,7 +1508,7 @@ namespace Umbraco.Core.Services.Implement HashSet culturesToPublish = null; if (!c.ContentType.VariesByCulture()) // invariant content type - return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); // variant content type, specific cultures if (c.Published) @@ -1380,7 +1516,7 @@ namespace Umbraco.Core.Services.Implement // then some (and maybe all) cultures will be 'already published' (unless forcing), // others will have to 'republish this culture' foreach (var x in cultures) - SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); + SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); return culturesToPublish; } @@ -1393,11 +1529,10 @@ namespace Umbraco.Core.Services.Implement return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId); } - /// - public IEnumerable SaveAndPublishBranch(IContent document, bool force, + internal IEnumerable SaveAndPublishBranch(IContent document, bool force, Func> shouldPublish, Func, bool> publishCultures, - int userId = 0) + int userId = Constants.Security.SuperUserId) { if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish)); if (publishCultures == null) throw new ArgumentNullException(nameof(publishCultures)); @@ -1413,12 +1548,12 @@ namespace Umbraco.Core.Services.Implement if (!document.HasIdentity) throw new InvalidOperationException("Cannot not branch-publish a new document."); - var publishedState = ((Content)document).PublishedState; + var publishedState = document.PublishedState; if (publishedState == PublishedState.Publishing) throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch."); // deal with the branch root - if it fails, abort - var result = SaveAndPublishBranchOne(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId); + var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId); if (result != null) { results.Add(result); @@ -1449,7 +1584,7 @@ namespace Umbraco.Core.Services.Implement } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchOne(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId); if (result != null) { results.Add(result); @@ -1466,8 +1601,9 @@ namespace Umbraco.Core.Services.Implement Audit(AuditType.Publish, userId, document.Id, "Branch published"); // trigger events for the entire branch + // (SaveAndPublishBranchOne does *not* do it) scope.Events.Dispatch(TreeChanged, this, new TreeChange(document, TreeChangeTypes.RefreshBranch).ToEventArgs()); - scope.Events.Dispatch(Published, this, new PublishEventArgs(publishedDocuments, false, false), "Published"); + scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(publishedDocuments, false, evtMsgs), nameof(Published)); scope.Complete(); } @@ -1478,7 +1614,7 @@ namespace Umbraco.Core.Services.Implement // shouldPublish: a function determining whether the document has changes that need to be published // note - 'force' is handled by 'editing' // publishValues: a function publishing values (using the appropriate PublishCulture calls) - private PublishResult SaveAndPublishBranchOne(IScope scope, IContent document, + private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document, Func> shouldPublish, Func, bool> publishCultures, bool isRoot, @@ -1491,11 +1627,18 @@ namespace Umbraco.Core.Services.Implement if (culturesToPublish.Count == 0) // empty = already published return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document); + var saveEventArgs = new ContentSavingEventArgs(document, evtMsgs); + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document); + // publish & check if values are valid if (!publishCultures(document, culturesToPublish)) + { + //TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); + } - var result = SavePublishingInternal(scope, document, userId, branchOne: true, branchRoot: isRoot); + var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot); if (result.Success) publishedDocuments.Add(document); return result; @@ -1574,7 +1717,7 @@ namespace Umbraco.Core.Services.Implement /// Id of the object to delete versions from /// Latest version date /// Optional Id of the User deleting versions of a Content object - public void DeleteVersions(int id, DateTime versionDate, int userId = 0) + public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -1604,7 +1747,7 @@ namespace Umbraco.Core.Services.Implement /// Id of the version to delete /// Boolean indicating whether to delete versions prior to the versionId /// Optional Id of the User deleting versions of a Content object - public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = 0) + public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -1690,7 +1833,7 @@ namespace Umbraco.Core.Services.Implement /// The to move /// Id of the Content's new Parent /// Optional Id of the User moving the Content - public void Move(IContent content, int parentId, int userId = 0) + public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId) { // if moving to the recycle bin then use the proper method if (parentId == Constants.System.RecycleBinContent) @@ -1729,7 +1872,7 @@ namespace Umbraco.Core.Services.Implement { // however, it had been masked when being trashed, so there's no need for // any special event here - just change its state - ((Content)content).PublishedState = PublishedState.Unpublishing; + content.PublishedState = PublishedState.Unpublishing; } PerformMoveLocked(content, parentId, parent, userId, moves, trashed); @@ -1866,7 +2009,7 @@ namespace Umbraco.Core.Services.Implement /// Boolean indicating whether the copy should be related to the original /// Optional Id of the User copying the Content /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) + public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = Constants.Security.SuperUserId) { return Copy(content, parentId, relateToOriginal, true, userId); } @@ -1881,7 +2024,7 @@ namespace Umbraco.Core.Services.Implement /// A value indicating whether to recursively copy children. /// Optional Id of the User copying the Content /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0) + public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Constants.Security.SuperUserId) { var copy = content.DeepCloneWithResetIdentities(); copy.ParentId = parentId; @@ -1906,7 +2049,7 @@ namespace Umbraco.Core.Services.Implement // a copy is not published (but not really unpublishing either) // update the create author and last edit author if (copy.Published) - ((Content)copy).Published = false; + copy.Published = false; copy.CreatorId = userId; copy.WriterId = userId; @@ -1950,7 +2093,7 @@ namespace Umbraco.Core.Services.Implement // a copy is not published (but not really unpublishing either) // update the create author and last edit author if (descendantCopy.Published) - ((Content)descendantCopy).Published = false; + descendantCopy.Published = false; descendantCopy.CreatorId = userId; descendantCopy.WriterId = userId; @@ -1984,7 +2127,7 @@ namespace Umbraco.Core.Services.Implement /// The to send to publication /// Optional Id of the User issuing the send to publication /// True if sending publication was successful otherwise false - public bool SendToPublication(IContent content, int userId = 0) + public bool SendToPublication(IContent content, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -1997,7 +2140,7 @@ namespace Umbraco.Core.Services.Implement //track the cultures changing for auditing var culturesChanging = content.ContentType.VariesByCulture() - ? string.Join(",", content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key)) + ? string.Join(",", content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture)) : null; // TODO: Currently there's no way to change track which variant properties have changed, we only have change @@ -2038,7 +2181,7 @@ namespace Umbraco.Core.Services.Implement /// /// /// Result indicating what action was taken when handling the command. - public OperationResult Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) + public OperationResult Sort(IEnumerable items, int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); @@ -2067,7 +2210,7 @@ namespace Umbraco.Core.Services.Implement /// /// /// Result indicating what action was taken when handling the command. - public OperationResult Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true) + public OperationResult Sort(IEnumerable ids, int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); @@ -2087,7 +2230,7 @@ namespace Umbraco.Core.Services.Implement private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents) { - var saveEventArgs = new SaveEventArgs(itemsA); + var saveEventArgs = new ContentSavingEventArgs(itemsA, evtMsgs); if (raiseEvents) { //raise cancelable sorting event @@ -2129,15 +2272,16 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents) { + var savedEventsArgs = saveEventArgs.ToContentSavedEventArgs(); //first saved, then sorted - scope.Events.Dispatch(Saved, this, saveEventArgs, nameof(Saved)); - scope.Events.Dispatch(Sorted, this, saveEventArgs, nameof(Sorted)); + scope.Events.Dispatch(Saved, this, savedEventsArgs, nameof(Saved)); + scope.Events.Dispatch(Sorted, this, savedEventsArgs, nameof(Sorted)); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); if (raiseEvents && published.Any()) - scope.Events.Dispatch(Published, this, new PublishEventArgs(published, false, false), "Published"); + scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(published, false, evtMsgs), "Published"); Audit(AuditType.Sort, userId, 0, "Sorting content performed by user"); return OperationResult.Succeed(evtMsgs); @@ -2228,21 +2372,12 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs before Save /// - public static event TypedEventHandler> Saving; + public static event TypedEventHandler Saving; /// /// Occurs after Save /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Content object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; + public static event TypedEventHandler Saved; /// /// Occurs before Copy @@ -2307,12 +2442,12 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs before publish /// - public static event TypedEventHandler> Publishing; + public static event TypedEventHandler Publishing; /// /// Occurs after publish /// - public static event TypedEventHandler> Published; + public static event TypedEventHandler Published; /// /// Occurs before unpublish @@ -2348,14 +2483,16 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// /// + /// /// + /// + /// /// - private PublishResult StrategyCanPublish(IScope scope, IContent content, int userId, bool checkPath, IReadOnlyList culturesPublishing, IReadOnlyList culturesUnpublishing, EventMessages evtMsgs) + private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs) { // raise Publishing event - if (scope.Events.DispatchCancelable(Publishing, this, new PublishEventArgs(content, evtMsgs))) + if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs())) { Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); @@ -2382,7 +2519,7 @@ namespace Umbraco.Core.Services.Implement // ensure that the document has published values // either because it is 'publishing' or because it already has a published version - if (((Content)content).PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0) + if (content.PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0) { Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document does not have published values"); return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); @@ -2438,20 +2575,20 @@ namespace Umbraco.Core.Services.Implement /// /// Publishes a document /// - /// /// - /// + /// /// + /// /// /// /// It is assumed that all publishing checks have passed before calling this method like /// - private PublishResult StrategyPublish(IScope scope, IContent content, int userId, - IReadOnlyList culturesPublishing, IReadOnlyList culturesUnpublishing, + private PublishResult StrategyPublish(IContent content, + IReadOnlyCollection culturesPublishing, IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs) { // change state to publishing - ((Content)content).PublishedState = PublishedState.Publishing; + content.PublishedState = PublishedState.Publishing; //if this is a variant then we need to log which cultures have been published/unpublished and return an appropriate result if (content.ContentType.VariesByCulture()) @@ -2476,7 +2613,6 @@ namespace Umbraco.Core.Services.Implement return new PublishResult(PublishResultType.SuccessPublishCulture, evtMsgs, content); } - Logger.Info("Document {ContentName} (id={ContentId}) has been published.", content.Name, content.Id); return new PublishResult(evtMsgs, content); } @@ -2486,10 +2622,9 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// /// /// - private PublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs) + private PublishResult StrategyCanUnpublish(IScope scope, IContent content, EventMessages evtMsgs) { // raise Unpublishing event if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs(content, evtMsgs))) @@ -2530,7 +2665,7 @@ namespace Umbraco.Core.Services.Implement Logger.Info("Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", content.Name, content.Id); // change state to unpublishing - ((Content)content).PublishedState = PublishedState.Unpublishing; + content.PublishedState = PublishedState.Unpublishing; Logger.Info("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name, content.Id); return attempt; @@ -2548,9 +2683,9 @@ namespace Umbraco.Core.Services.Implement /// Deletes content items of the specified type, and only that type. Does *not* handle content types /// inheritance and compositions, which need to be managed outside of this method. /// - /// Id of the + /// Id of the /// Optional Id of the user issuing the delete operation - public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = 0) + public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId) { // TODO: This currently this is called from the ContentTypeService but that needs to change, // if we are deleting a content type, we should just delete the data and do this operation slightly differently. @@ -2626,7 +2761,7 @@ namespace Umbraco.Core.Services.Implement /// This needs extra care and attention as its potentially a dangerous and extensive operation /// Id of the /// Optional id of the user deleting the media - public void DeleteOfType(int contentTypeId, int userId = 0) + public void DeleteOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) { DeleteOfTypes(new[] { contentTypeId }, userId); } @@ -2667,7 +2802,7 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.ContentTree); var blueprint = _documentBlueprintRepository.Get(id); if (blueprint != null) - ((Content)blueprint).Blueprint = true; + blueprint.Blueprint = true; return blueprint; } } @@ -2679,18 +2814,18 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.ContentTree); var blueprint = _documentBlueprintRepository.Get(id); if (blueprint != null) - ((Content)blueprint).Blueprint = true; + blueprint.Blueprint = true; return blueprint; } } - public void SaveBlueprint(IContent content, int userId = 0) + public void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId) { //always ensure the blueprint is at the root if (content.ParentId != -1) content.ParentId = -1; - ((Content)content).Blueprint = true; + content.Blueprint = true; using (var scope = ScopeProvider.CreateScope()) { @@ -2710,7 +2845,7 @@ namespace Umbraco.Core.Services.Implement } } - public void DeleteBlueprint(IContent content, int userId = 0) + public void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -2723,11 +2858,11 @@ namespace Umbraco.Core.Services.Implement private static readonly string[] ArrayOfOneNullString = { null }; - public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0) + public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId) { if (blueprint == null) throw new ArgumentNullException(nameof(blueprint)); - var contentType = blueprint.ContentType; + var contentType = GetContentType(blueprint.ContentType.Alias); var content = new Content(name, -1, contentType); content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); @@ -2735,7 +2870,7 @@ namespace Umbraco.Core.Services.Implement content.WriterId = userId; var now = DateTime.Now; - var cultures = blueprint.CultureInfos.Any() ? blueprint.CultureInfos.Select(x=>x.Key) : ArrayOfOneNullString; + var cultures = blueprint.CultureInfos.Count > 0 ? blueprint.CultureInfos.Values.Select(x => x.Culture) : ArrayOfOneNullString; foreach (var culture in cultures) { foreach (var property in blueprint.Properties) @@ -2766,13 +2901,13 @@ namespace Umbraco.Core.Services.Implement } return _documentBlueprintRepository.Get(query).Select(x => { - ((Content)x).Blueprint = true; + x.Blueprint = true; return x; }); } } - public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0) + public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -2785,7 +2920,7 @@ namespace Umbraco.Core.Services.Implement var blueprints = _documentBlueprintRepository.Get(query).Select(x => { - ((Content)x).Blueprint = true; + x.Blueprint = true; return x; }).ToArray(); @@ -2799,7 +2934,7 @@ namespace Umbraco.Core.Services.Implement } } - public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0) + public void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) { DeleteBlueprintsOfTypes(new[] { contentTypeId }, userId); } @@ -2808,7 +2943,7 @@ namespace Umbraco.Core.Services.Implement #region Rollback - public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0) + public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeBaseServiceProvider.cs b/src/Umbraco.Core/Services/Implement/ContentTypeBaseServiceProvider.cs new file mode 100644 index 0000000000..5a56dfe3bc --- /dev/null +++ b/src/Umbraco.Core/Services/Implement/ContentTypeBaseServiceProvider.cs @@ -0,0 +1,42 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services.Implement +{ + public class ContentTypeBaseServiceProvider : IContentTypeBaseServiceProvider + { + private readonly IContentTypeService _contentTypeService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + + public ContentTypeBaseServiceProvider(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService) + { + _contentTypeService = contentTypeService; + _mediaTypeService = mediaTypeService; + _memberTypeService = memberTypeService; + } + + public IContentTypeBaseService For(IContentBase contentBase) + { + if (contentBase == null) throw new ArgumentNullException(nameof(contentBase)); + switch (contentBase) + { + case IContent _: + return _contentTypeService; + case IMedia _: + return _mediaTypeService; + case IMember _: + return _memberTypeService; + default: + throw new ArgumentException($"Invalid contentBase type: {contentBase.GetType().FullName}" , nameof(contentBase)); + } + } + + // note: this should be a default interface method with C# 8 + public IContentTypeComposition GetContentTypeOf(IContentBase contentBase) + { + if (contentBase == null) throw new ArgumentNullException(nameof(contentBase)); + return For(contentBase)?.Get(contentBase.ContentTypeId); + } + } +} diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs index f4457e0991..3a1ad64483 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Services.Implement { public abstract class ContentTypeServiceBase : ContentTypeServiceBase where TItem : class, IContentTypeComposition - where TService : class, IContentTypeServiceBase + where TService : class, IContentTypeBaseService { protected ContentTypeServiceBase(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, logger, eventMessagesFactory) diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 78bb9821e4..1f1f0d9ac3 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -13,10 +13,10 @@ using Umbraco.Core.Services.Changes; namespace Umbraco.Core.Services.Implement { - public abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeServiceBase + public abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeBaseService where TRepository : IContentTypeRepositoryBase where TItem : class, IContentTypeComposition - where TService : class, IContentTypeServiceBase + where TService : class, IContentTypeBaseService { private readonly IAuditRepository _auditRepository; private readonly IEntityContainerRepository _containerRepository; @@ -211,6 +211,11 @@ namespace Umbraco.Core.Services.Implement #region Get, Has, Is, Count + IContentTypeComposition IContentTypeBaseService.Get(int id) + { + return Get(id); + } + public TItem Get(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -371,7 +376,7 @@ namespace Umbraco.Core.Services.Implement #region Save - public void Save(TItem item, int userId = 0) + public void Save(TItem item, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -409,7 +414,7 @@ namespace Umbraco.Core.Services.Implement } } - public void Save(IEnumerable items, int userId = 0) + public void Save(IEnumerable items, int userId = Constants.Security.SuperUserId) { var itemsA = items.ToArray(); @@ -455,7 +460,7 @@ namespace Umbraco.Core.Services.Implement #region Delete - public void Delete(TItem item, int userId = 0) + public void Delete(TItem item, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -509,7 +514,7 @@ namespace Umbraco.Core.Services.Implement } } - public void Delete(IEnumerable items, int userId = 0) + public void Delete(IEnumerable items, int userId = Constants.Security.SuperUserId) { var itemsA = items.ToArray(); @@ -730,7 +735,7 @@ namespace Umbraco.Core.Services.Implement protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType); - public Attempt> CreateContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContainer(int parentId, string name, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -770,7 +775,7 @@ namespace Umbraco.Core.Services.Implement } } - public Attempt SaveContainer(EntityContainer container, int userId = 0) + public Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); @@ -864,7 +869,7 @@ namespace Umbraco.Core.Services.Implement } } - public Attempt DeleteContainer(int containerId, int userId = 0) + public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -901,7 +906,7 @@ namespace Umbraco.Core.Services.Implement } } - public Attempt> RenameContainer(int id, string name, int userId = 0) + public Attempt> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -951,5 +956,7 @@ namespace Umbraco.Core.Services.Implement } #endregion + + } } diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs index 97368e9047..445daddff4 100644 --- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Services.Implement #region Containers - public Attempt> CreateContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContainer(int parentId, string name, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -119,7 +119,7 @@ namespace Umbraco.Core.Services.Implement } } - public Attempt SaveContainer(EntityContainer container, int userId = 0) + public Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); @@ -153,7 +153,7 @@ namespace Umbraco.Core.Services.Implement return OperationResult.Attempt.Succeed(evtMsgs); } - public Attempt DeleteContainer(int containerId, int userId = 0) + public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -186,7 +186,7 @@ namespace Umbraco.Core.Services.Implement return OperationResult.Attempt.Succeed(evtMsgs); } - public Attempt> RenameContainer(int id, string name, int userId = 0) + public Attempt> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -331,7 +331,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// Id of the user issuing the save - public void Save(IDataType dataType, int userId = 0) + public void Save(IDataType dataType, int userId = Constants.Security.SuperUserId) { dataType.CreatorId = userId; @@ -363,7 +363,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// Id of the user issuing the save - public void Save(IEnumerable dataTypeDefinitions, int userId = 0) + public void Save(IEnumerable dataTypeDefinitions, int userId = Constants.Security.SuperUserId) { Save(dataTypeDefinitions, userId, true); } @@ -413,7 +413,7 @@ namespace Umbraco.Core.Services.Implement /// /// to delete /// Optional Id of the user issuing the deletion - public void Delete(IDataType dataType, int userId = 0) + public void Delete(IDataType dataType, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { diff --git a/src/Umbraco.Core/Services/Implement/FileService.cs b/src/Umbraco.Core/Services/Implement/FileService.cs index d85b0ca1ba..596d033ae8 100644 --- a/src/Umbraco.Core/Services/Implement/FileService.cs +++ b/src/Umbraco.Core/Services/Implement/FileService.cs @@ -74,7 +74,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// - public void SaveStylesheet(Stylesheet stylesheet, int userId = 0) + public void SaveStylesheet(Stylesheet stylesheet, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -100,7 +100,7 @@ namespace Umbraco.Core.Services.Implement /// /// Name incl. extension of the Stylesheet to delete /// - public void DeleteStylesheet(string path, int userId = 0) + public void DeleteStylesheet(string path, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -217,7 +217,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// - public void SaveScript(Script script, int userId = 0) + public void SaveScript(Script script, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -242,7 +242,7 @@ namespace Umbraco.Core.Services.Implement /// /// Name incl. extension of the Script to delete /// - public void DeleteScript(string path, int userId = 0) + public void DeleteScript(string path, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -338,7 +338,7 @@ namespace Umbraco.Core.Services.Implement /// /// The template created /// - public Attempt> CreateTemplateForContentType(string contentTypeAlias, string contentTypeName, int userId = 0) + public Attempt> CreateTemplateForContentType(string contentTypeAlias, string contentTypeName, int userId = Constants.Security.SuperUserId) { var template = new Template(contentTypeName, //NOTE: We are NOT passing in the content type alias here, we want to use it's name since we don't @@ -395,7 +395,7 @@ namespace Umbraco.Core.Services.Implement /// /// /// - public ITemplate CreateTemplateWithIdentity(string name, string alias, string content, ITemplate masterTemplate = null, int userId = 0) + public ITemplate CreateTemplateWithIdentity(string name, string alias, string content, ITemplate masterTemplate = null, int userId = Constants.Security.SuperUserId) { // file might already be on disk, if so grab the content to avoid overwriting var template = new Template(name, alias) @@ -529,7 +529,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// - public void SaveTemplate(ITemplate template, int userId = 0) + public void SaveTemplate(ITemplate template, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -553,7 +553,7 @@ namespace Umbraco.Core.Services.Implement /// /// List of to save /// Optional id of the user - public void SaveTemplate(IEnumerable templates, int userId = 0) + public void SaveTemplate(IEnumerable templates, int userId = Constants.Security.SuperUserId) { var templatesA = templates.ToArray(); using (var scope = ScopeProvider.CreateScope()) @@ -579,7 +579,7 @@ namespace Umbraco.Core.Services.Implement /// /// Alias of the to delete /// - public void DeleteTemplate(string alias, int userId = 0) + public void DeleteTemplate(string alias, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -723,17 +723,17 @@ namespace Umbraco.Core.Services.Implement } } - public Attempt CreatePartialView(IPartialView partialView, string snippetName = null, int userId = 0) + public Attempt CreatePartialView(IPartialView partialView, string snippetName = null, int userId = Constants.Security.SuperUserId) { return CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId); } - public Attempt CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = 0) + public Attempt CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = Constants.Security.SuperUserId) { return CreatePartialViewMacro(partialView, PartialViewType.PartialViewMacro, snippetName, userId); } - private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, string snippetName = null, int userId = 0) + private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, string snippetName = null, int userId = Constants.Security.SuperUserId) { string partialViewHeader; switch (partialViewType) @@ -799,17 +799,17 @@ namespace Umbraco.Core.Services.Implement return Attempt.Succeed(partialView); } - public bool DeletePartialView(string path, int userId = 0) + public bool DeletePartialView(string path, int userId = Constants.Security.SuperUserId) { return DeletePartialViewMacro(path, PartialViewType.PartialView, userId); } - public bool DeletePartialViewMacro(string path, int userId = 0) + public bool DeletePartialViewMacro(string path, int userId = Constants.Security.SuperUserId) { return DeletePartialViewMacro(path, PartialViewType.PartialViewMacro, userId); } - private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int userId = 0) + private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -839,17 +839,17 @@ namespace Umbraco.Core.Services.Implement return true; } - public Attempt SavePartialView(IPartialView partialView, int userId = 0) + public Attempt SavePartialView(IPartialView partialView, int userId = Constants.Security.SuperUserId) { return SavePartialView(partialView, PartialViewType.PartialView, userId); } - public Attempt SavePartialViewMacro(IPartialView partialView, int userId = 0) + public Attempt SavePartialViewMacro(IPartialView partialView, int userId = Constants.Security.SuperUserId) { return SavePartialView(partialView, PartialViewType.PartialViewMacro, userId); } - private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, int userId = 0) + private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { diff --git a/src/Umbraco.Core/Services/Implement/LocalizationService.cs b/src/Umbraco.Core/Services/Implement/LocalizationService.cs index 678ea10da2..251261cfc8 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizationService.cs @@ -227,7 +227,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// Optional id of the user saving the dictionary item - public void Save(IDictionaryItem dictionaryItem, int userId = 0) + public void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -256,7 +256,7 @@ namespace Umbraco.Core.Services.Implement /// /// to delete /// Optional id of the user deleting the dictionary item - public void Delete(IDictionaryItem dictionaryItem, int userId = 0) + public void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -356,7 +356,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// Optional id of the user saving the language - public void Save(ILanguage language, int userId = 0) + public void Save(ILanguage language, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -409,7 +409,7 @@ namespace Umbraco.Core.Services.Implement /// /// to delete /// Optional id of the user deleting the language - public void Delete(ILanguage language, int userId = 0) + public void Delete(ILanguage language, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { diff --git a/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs index a0b952a75a..f620d31fe9 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs @@ -61,7 +61,7 @@ namespace Umbraco.Core.Services.Implement var filename = Path.GetFileNameWithoutExtension(localCopy.FullName).Replace("_", "-"); // TODO: Fix this nonsense... would have to wait until v8 to store the language files with their correct - // names instead of storing them as 2 letters but actually having a 4 letter culture. wtf. So now, we + // names instead of storing them as 2 letters but actually having a 4 letter culture. So now, we // need to check if the file is 2 letters, then open it to try to find it's 4 letter culture, then use that // if it's successful. We're going to assume (though it seems assuming in the legacy logic is never a great idea) // that any 4 letter file is named with the actual culture that it is! diff --git a/src/Umbraco.Core/Services/Implement/MacroService.cs b/src/Umbraco.Core/Services/Implement/MacroService.cs index d4f2d95bbb..a6631aae4c 100644 --- a/src/Umbraco.Core/Services/Implement/MacroService.cs +++ b/src/Umbraco.Core/Services/Implement/MacroService.cs @@ -81,7 +81,7 @@ namespace Umbraco.Core.Services.Implement /// /// to delete /// Optional id of the user deleting the macro - public void Delete(IMacro macro, int userId = 0) + public void Delete(IMacro macro, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -106,7 +106,7 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// Optional Id of the user deleting the macro - public void Save(IMacro macro, int userId = 0) + public void Save(IMacro macro, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index e7a42e28e4..a1aea45f11 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -112,7 +112,7 @@ namespace Umbraco.Core.Services.Implement /// Alias of the /// Optional id of the user creating the media item /// - public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = 0) + public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { var parent = GetById(parentId); return CreateMedia(name, parent, mediaTypeAlias, userId); @@ -130,7 +130,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0) + public IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { var mediaType = GetMediaType(mediaTypeAlias); if (mediaType == null) @@ -160,7 +160,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = 0) + public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { // not locking since not saving anything @@ -190,7 +190,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) + public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { if (parent == null) throw new ArgumentNullException(nameof(parent)); @@ -219,7 +219,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0) + public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -251,7 +251,7 @@ namespace Umbraco.Core.Services.Implement /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) + public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { if (parent == null) throw new ArgumentNullException(nameof(parent)); @@ -289,9 +289,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Saved, this, saveEventArgs); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.RefreshNode).ToEventArgs()); } - - scope.Events.Dispatch(Created, this, new NewEventArgs(media, false, media.ContentType.Alias, parent)); - + if (withIdentity == false) return; @@ -632,7 +630,7 @@ namespace Umbraco.Core.Services.Implement /// The to save /// Id of the User saving the Media /// Optional boolean indicating whether or not to raise events. - public Attempt Save(IMedia media, int userId = 0, bool raiseEvents = true) + public Attempt Save(IMedia media, int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); @@ -677,7 +675,7 @@ namespace Umbraco.Core.Services.Implement /// Collection of to save /// Id of the User saving the Media /// Optional boolean indicating whether or not to raise events. - public Attempt Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) + public Attempt Save(IEnumerable medias, int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); var mediasA = medias.ToArray(); @@ -724,7 +722,7 @@ namespace Umbraco.Core.Services.Implement /// /// The to delete /// Id of the User deleting the Media - public Attempt Delete(IMedia media, int userId = 0) + public Attempt Delete(IMedia media, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); @@ -785,7 +783,7 @@ namespace Umbraco.Core.Services.Implement /// Id of the object to delete versions from /// Latest version date /// Optional Id of the User deleting versions of a Media object - public void DeleteVersions(int id, DateTime versionDate, int userId = 0) + public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -809,7 +807,7 @@ namespace Umbraco.Core.Services.Implement } } - private void DeleteVersions(IScope scope, bool wlock, int id, DateTime versionDate, int userId = 0) + private void DeleteVersions(IScope scope, bool wlock, int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) { var args = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); if (scope.Events.DispatchCancelable(DeletingVersions, this, args)) @@ -832,7 +830,7 @@ namespace Umbraco.Core.Services.Implement /// Id of the version to delete /// Boolean indicating whether to delete versions prior to the versionId /// Optional Id of the User deleting versions of a Media object - public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = 0) + public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { @@ -872,7 +870,7 @@ namespace Umbraco.Core.Services.Implement /// /// The to delete /// Id of the User deleting the Media - public Attempt MoveToRecycleBin(IMedia media, int userId = 0) + public Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); var moves = new List>(); @@ -916,7 +914,7 @@ namespace Umbraco.Core.Services.Implement /// The to move /// Id of the Media's new Parent /// Id of the User moving the Media - public Attempt Move(IMedia media, int parentId, int userId = 0) + public Attempt Move(IMedia media, int parentId, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); @@ -1084,7 +1082,7 @@ namespace Umbraco.Core.Services.Implement /// /// /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) + public bool Sort(IEnumerable items, int userId = Constants.Security.SuperUserId, bool raiseEvents = true) { var itemsA = items.ToArray(); if (itemsA.Length == 0) return true; @@ -1215,15 +1213,6 @@ namespace Umbraco.Core.Services.Implement /// public static event TypedEventHandler> Saved; - /// - /// Occurs after Create - /// - /// - /// Please note that the Media object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - /// /// Occurs before Media is moved to Recycle Bin /// @@ -1273,7 +1262,7 @@ namespace Umbraco.Core.Services.Implement /// /// Id of the /// Optional id of the user deleting the media - public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = 0) + public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Constants.Security.SuperUserId) { // TODO: This currently this is called from the ContentTypeService but that needs to change, // if we are deleting a content type, we should just delete the data and do this operation slightly differently. @@ -1338,7 +1327,7 @@ namespace Umbraco.Core.Services.Implement /// This needs extra care and attention as its potentially a dangerous and extensive operation /// Id of the /// Optional id of the user deleting the media - public void DeleteMediaOfType(int mediaTypeId, int userId = 0) + public void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId) { DeleteMediaOfTypes(new[] { mediaTypeId }, userId); } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 3b35896769..8c69664712 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -331,9 +331,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); } - - scope.Events.Dispatch(Created, this, new NewEventArgs(member, false, member.ContentType.Alias, -1)); - + if (withIdentity == false) return; @@ -1110,15 +1108,6 @@ namespace Umbraco.Core.Services.Implement /// public static event TypedEventHandler> Saving; - /// - /// Occurs after Create - /// - /// - /// Please note that the Member object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - /// /// Occurs after Save /// @@ -1186,7 +1175,7 @@ namespace Umbraco.Core.Services.Implement var identity = int.MaxValue; var memType = new MemberType(-1); - var propGroup = new PropertyGroup(MemberType.IsPublishingConst) + var propGroup = new PropertyGroup(MemberType.SupportsPublishingConst) { Name = "Membership", Id = --identity @@ -1212,21 +1201,21 @@ namespace Umbraco.Core.Services.Implement Id = --identity, Key = identity.ToGuid() }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, Constants.Conventions.Member.LastLockoutDate) + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, Constants.Conventions.Member.LastLockoutDate) { Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 5, Id = --identity, Key = identity.ToGuid() }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, Constants.Conventions.Member.LastLoginDate) + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, Constants.Conventions.Member.LastLoginDate) { Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 6, Id = --identity, Key = identity.ToGuid() }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, Constants.Conventions.Member.LastPasswordChangeDate) + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, Constants.Conventions.Member.LastPasswordChangeDate) { Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 7, diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index d981809364..2b21945ba8 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -345,8 +345,8 @@ namespace Umbraco.Core.Services.Implement { //Create the HTML based summary (ul of culture names) - var culturesChanged = content.CultureInfos.Where(x => x.Value.WasDirty()) - .Select(x => x.Key) + var culturesChanged = content.CultureInfos.Values.Where(x => x.WasDirty()) + .Select(x => x.Culture) .Select(_localizationService.GetLanguageByIsoCode) .WhereNotNull() .Select(x => x.CultureName); @@ -363,8 +363,8 @@ namespace Umbraco.Core.Services.Implement { //Create the text based summary (csv of culture names) - var culturesChanged = string.Join(", ", content.CultureInfos.Where(x => x.Value.WasDirty()) - .Select(x => x.Key) + var culturesChanged = string.Join(", ", content.CultureInfos.Values.Where(x => x.WasDirty()) + .Select(x => x.Culture) .Select(_localizationService.GetLanguageByIsoCode) .WhereNotNull() .Select(x => x.CultureName)); diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index 9c4c6290a9..b4dcc70a96 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -3,26 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; -using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Web; -using System.Xml.Linq; using Semver; -using Umbraco.Core.Collections; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; -using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Packaging; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Scoping; -using Umbraco.Core.Strings; -using Content = Umbraco.Core.Models.Content; namespace Umbraco.Core.Services.Implement { @@ -100,7 +88,7 @@ namespace Umbraco.Core.Services.Implement public CompiledPackage GetCompiledPackageInfo(FileInfo packageFile) => _packageInstallation.ReadPackage(packageFile); - public IEnumerable InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0) + public IEnumerable InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = Constants.Security.SuperUserId) { if (packageDefinition == null) throw new ArgumentNullException(nameof(packageDefinition)); if (packageDefinition.Id == default) throw new ArgumentException("The package definition has not been persisted"); @@ -118,7 +106,7 @@ namespace Umbraco.Core.Services.Implement return files; } - public InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0) + public InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = Constants.Security.SuperUserId) { if (packageDefinition == null) throw new ArgumentNullException(nameof(packageDefinition)); if (packageDefinition.Id == default) throw new ArgumentException("The package definition has not been persisted"); @@ -141,7 +129,7 @@ namespace Umbraco.Core.Services.Implement return summary; } - public UninstallationSummary UninstallPackage(string packageName, int userId = 0) + public UninstallationSummary UninstallPackage(string packageName, int userId = Constants.Security.SuperUserId) { //this is ordered by descending version var allPackageVersions = GetInstalledPackageByName(packageName)?.ToList(); @@ -187,7 +175,7 @@ namespace Umbraco.Core.Services.Implement #region Created/Installed Package Repositories - public void DeleteCreatedPackage(int id, int userId = 0) + public void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId) { var package = GetCreatedPackageById(id); if (package == null) return; @@ -236,7 +224,7 @@ namespace Umbraco.Core.Services.Implement public bool SaveInstalledPackage(PackageDefinition definition) => _installedPackages.SavePackage(definition); - public void DeleteInstalledPackage(int packageId, int userId = 0) + public void DeleteInstalledPackage(int packageId, int userId = Constants.Security.SuperUserId) { var package = GetInstalledPackageById(packageId); if (package == null) return; diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs new file mode 100644 index 0000000000..146173dd5c --- /dev/null +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Collections; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Services +{ + //TODO: We should make this an interface and inject it into the ContentService + internal class PropertyValidationService + { + private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; + + public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) + { + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + } + + //TODO: Remove this method in favor of the overload specifying all dependencies + public PropertyValidationService() + : this(Current.PropertyEditors, Current.Services.DataTypeService) + { + } + + /// + /// Validates the content item's properties pass validation rules + /// + /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor + /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. + public bool IsPropertyDataValid(IContentBase content, out Property[] invalidProperties, string culture = "*") + { + // select invalid properties + invalidProperties = content.Properties.Where(x => + { + // if culture is null, we validate invariant properties only + // if culture is '*' we validate both variant and invariant properties, automatically + // if culture is specific eg 'en-US' we both too, but explicitly + + var varies = x.PropertyType.VariesByCulture(); + + if (culture == null) + return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture + + if (culture == "*") + return !IsPropertyValid(x, culture); // validate property, all cultures + + return varies + ? !IsPropertyValid(x, culture) // validate variant property, explicit culture + : !IsPropertyValid(x, null); // validate invariant property, explicit culture + }) + .ToArray(); + + return invalidProperties.Length == 0; + } + + /// + /// Gets a value indicating whether the property has valid values. + /// + public bool IsPropertyValid(Property property, string culture = "*", string segment = "*") + { + //NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there. + // The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing. + + culture = culture.NullOrWhiteSpaceAsNull(); + segment = segment.NullOrWhiteSpaceAsNull(); + + Property.PropertyValue pvalue = null; + + // if validating invariant/neutral, and it is supported, validate + // (including ensuring that the value exists, if mandatory) + if ((culture == null || culture == "*") && (segment == null || segment == "*") && property.PropertyType.SupportsVariation(null, null)) + { + // validate pvalue (which is the invariant value) + pvalue = property.Values.FirstOrDefault(x => x.Culture == null && x.Segment == null); + if (!IsValidPropertyValue(property, pvalue?.EditedValue)) + return false; + } + + // if validating only invariant/neutral, we are good + if (culture == null && segment == null) + return true; + + // if nothing else to validate, we are good + if ((culture == null || culture == "*") && (segment == null || segment == "*") && !property.PropertyType.VariesByCulture()) + return true; + + // for anything else, validate the existing values (including mandatory), + // but we cannot validate mandatory globally (we don't know the possible cultures and segments) + + // validate vvalues (which are the variant values) + + // if we don't have vvalues (property.Values is empty or only contains pvalue), validate null + if (property.Values.Count == (pvalue == null ? 0 : 1)) + return culture == "*" || IsValidPropertyValue(property, null); + + // else validate vvalues (but don't revalidate pvalue) + var pvalues = property.Values.Where(x => + x != pvalue && // don't revalidate pvalue + property.PropertyType.SupportsVariation(x.Culture, x.Segment, true) && // the value variation is ok + (culture == "*" || x.Culture.InvariantEquals(culture)) && // the culture matches + (segment == "*" || x.Segment.InvariantEquals(segment))) // the segment matches + .ToList(); + + return pvalues.Count == 0 || pvalues.All(x => IsValidPropertyValue(property, x.EditedValue)); + } + + /// + /// Boolean indicating whether the passed in value is valid + /// + /// + /// + /// True is property value is valid, otherwise false + private bool IsValidPropertyValue(Property property, object value) + { + return IsPropertyValueValid(property.PropertyType, value); + } + + /// + /// Determines whether a value is valid for this property type. + /// + private bool IsPropertyValueValid(PropertyType propertyType, object value) + { + var editor = _propertyEditors[propertyType.PropertyEditorAlias]; + var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId).Configuration; + var valueEditor = editor.GetValueEditor(configuration); + return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.ValidationRegExp).Any(); + } + } +} diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 6d7ac8a5e7..f3c95b07f9 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -32,11 +32,12 @@ namespace Umbraco.Core.Services private readonly Lazy _externalLoginService; private readonly Lazy _redirectUrlService; private readonly Lazy _consentService; + private readonly Lazy _contentTypeBaseServiceProvider; /// /// Initializes a new instance of the class with lazy services. /// - public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService) + public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService, Lazy contentTypeBaseServiceProvider) { _publicAccessService = publicAccessService; _domainService = domainService; @@ -63,6 +64,7 @@ namespace Umbraco.Core.Services _externalLoginService = externalLoginService; _redirectUrlService = redirectUrlService; _consentService = consentService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; } /// @@ -96,7 +98,8 @@ namespace Umbraco.Core.Services IExternalLoginService externalLoginService = null, IServerRegistrationService serverRegistrationService = null, IRedirectUrlService redirectUrlService = null, - IConsentService consentService = null) + IConsentService consentService = null, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider = null) { Lazy Lazy(T service) => service == null ? null : new Lazy(() => service); @@ -125,7 +128,9 @@ namespace Umbraco.Core.Services Lazy(notificationService), Lazy(externalLoginService), Lazy(redirectUrlService), - Lazy(consentService)); + Lazy(consentService), + Lazy(contentTypeBaseServiceProvider) + ); } /// @@ -252,5 +257,10 @@ namespace Umbraco.Core.Services /// Gets the ConsentService. /// public IConsentService ConsentService => _consentService.Value; + + /// + /// Gets the ContentTypeServiceBaseFactory. + /// + public IContentTypeBaseServiceProvider ContentTypeBaseServices => _contentTypeBaseServiceProvider.Value; } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index b80149a4ef..4df1105bf7 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -593,13 +593,14 @@ namespace Umbraco.Core /// public static string ToUrlBase64(this string input) { - if (input == null) throw new ArgumentNullException("input"); + if (input == null) throw new ArgumentNullException(nameof(input)); - if (String.IsNullOrEmpty(input)) return String.Empty; + if (string.IsNullOrEmpty(input)) + return string.Empty; + //return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); var bytes = Encoding.UTF8.GetBytes(input); return UrlTokenEncode(bytes); - //return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); } /// @@ -609,14 +610,14 @@ namespace Umbraco.Core /// public static string FromUrlBase64(this string input) { - if (input == null) throw new ArgumentNullException("input"); + if (input == null) throw new ArgumentNullException(nameof(input)); //if (input.IsInvalidBase64()) return null; try { //var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "=")); - byte[] decodedBytes = UrlTokenDecode(input); + var decodedBytes = UrlTokenDecode(input); return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null; } catch (FormatException) @@ -795,42 +796,40 @@ namespace Umbraco.Core internal static byte[] UrlTokenDecode(string input) { if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (input.Length == 0) + return Array.Empty(); + + // calc array size - must be groups of 4 + var arrayLength = input.Length; + var remain = arrayLength % 4; + if (remain != 0) arrayLength += 4 - remain; + + var inArray = new char[arrayLength]; + for (var i = 0; i < input.Length; i++) { - throw new ArgumentNullException("input"); - } - int length = input.Length; - if (length < 1) - { - return new byte[0]; - } - int num2 = input[length - 1] - '0'; - if ((num2 < 0) || (num2 > 10)) - { - return null; - } - char[] inArray = new char[(length - 1) + num2]; - for (int i = 0; i < (length - 1); i++) - { - char ch = input[i]; + var ch = input[i]; switch (ch) { - case '-': + case '-': // restore '-' as '+' inArray[i] = '+'; break; - case '_': + case '_': // restore '_' as '/' inArray[i] = '/'; break; - default: + default: // keep char unchanged inArray[i] = ch; break; } } - for (int j = length - 1; j < inArray.Length; j++) - { + + // pad with '=' + for (var j = input.Length; j < inArray.Length; j++) inArray[j] = '='; - } + return Convert.FromBase64CharArray(inArray, 0, inArray.Length); } @@ -842,54 +841,40 @@ namespace Umbraco.Core internal static string UrlTokenEncode(byte[] input) { if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (input.Length == 0) + return string.Empty; + + // base-64 digits are A-Z, a-z, 0-9, + and / + // the = char is used for trailing padding + + var str = Convert.ToBase64String(input); + + var pos = str.IndexOf('='); + if (pos < 0) pos = str.Length; + + // replace chars that would cause problems in urls + var chArray = new char[pos]; + for (var i = 0; i < pos; i++) { - throw new ArgumentNullException("input"); - } - if (input.Length < 1) - { - return String.Empty; - } - string str = null; - int index = 0; - char[] chArray = null; - str = Convert.ToBase64String(input); - if (str == null) - { - return null; - } - index = str.Length; - while (index > 0) - { - if (str[index - 1] != '=') - { - break; - } - index--; - } - chArray = new char[index + 1]; - chArray[index] = (char)((0x30 + str.Length) - index); - for (int i = 0; i < index; i++) - { - char ch = str[i]; + var ch = str[i]; switch (ch) { - case '+': + case '+': // replace '+' with '-' chArray[i] = '-'; break; - case '/': + case '/': // replace '/' with '_' chArray[i] = '_'; break; - case '=': - chArray[i] = ch; - break; - - default: + default: // keep char unchanged chArray[i] = ch; break; } } + return new string(chArray); } diff --git a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs index 91dd5a7597..54ca74d20f 100644 --- a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs +++ b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Strings /// Converts an Utf8 string into an Ascii string. /// /// The text to convert. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The converted text. public static string ToAsciiString(string text, char fail = '?') { @@ -39,7 +39,7 @@ namespace Umbraco.Core.Strings /// Converts an Utf8 string into an array of Ascii characters. /// /// The text to convert. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The converted text. public static char[] ToAsciiCharArray(string text, char fail = '?') { @@ -66,7 +66,7 @@ namespace Umbraco.Core.Strings /// /// The input array. /// The output array. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The number of characters in the output array. /// The caller must ensure that the output array is big enough. /// The output array is not big enough. @@ -112,7 +112,7 @@ namespace Umbraco.Core.Strings /// The input position. /// The output array. /// The output position. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// /// Adapted from various sources on the 'net including Lucene.Net.Analysis.ASCIIFoldingFilter. /// Input should contain Utf8 characters exclusively and NOT Unicode. diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 741cc2bab1..7442169b44 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -534,27 +534,7 @@ namespace Umbraco.Core.Sync { var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt"; - string distCacheFilePath; - switch (globalSettings.LocalTempStorageLocation) - { - case LocalTempStorage.AspNetTemp: - distCacheFilePath = Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData", fileName); - break; - case LocalTempStorage.EnvironmentTemp: - var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); - var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", - //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back - // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not - // utilizing an old path - appDomainHash); - distCacheFilePath = Path.Combine(cachePath, fileName); - break; - case LocalTempStorage.Default: - default: - var tempFolder = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "DistCache"); - distCacheFilePath = Path.Combine(tempFolder, fileName); - break; - } + var distCacheFilePath = Path.Combine(globalSettings.LocalTempPath, "DistCache", fileName); //ensure the folder exists var folder = Path.GetDirectoryName(distCacheFilePath); diff --git a/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs b/src/Umbraco.Core/TypeLoaderExtensions.cs similarity index 93% rename from src/Umbraco.Core/Composing/TypeLoaderExtensions.cs rename to src/Umbraco.Core/TypeLoaderExtensions.cs index 5d17b0f53c..96028daca6 100644 --- a/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs +++ b/src/Umbraco.Core/TypeLoaderExtensions.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.PackageActions; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; -namespace Umbraco.Core.Composing +namespace Umbraco.Core { internal static class TypeLoaderExtensions { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4e6e832294..fa046acd63 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -55,25 +55,25 @@ - - + + 2.2.2 - 5.2.6 + 5.2.7 - 4.0.0 + 4.0.1 - - + + - 2.7.1 + 2.8.0 2.0.1 @@ -88,7 +88,7 @@ 1.0.0 - 1.0.2 + 1.0.3 2.2.2 @@ -112,7 +112,7 @@ - + @@ -157,35 +157,38 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + @@ -203,7 +206,16 @@ - + + + + + + + + + + @@ -285,9 +297,10 @@ + - + @@ -298,6 +311,12 @@ + + + + + + @@ -335,13 +354,16 @@ - + + + + @@ -358,6 +380,7 @@ + @@ -369,6 +392,7 @@ + @@ -376,6 +400,8 @@ + + @@ -390,6 +416,7 @@ + @@ -410,6 +437,7 @@ + @@ -432,7 +460,8 @@ - + + @@ -461,6 +490,7 @@ + @@ -501,7 +531,7 @@ - + @@ -541,7 +571,6 @@ - @@ -647,19 +676,13 @@ - - - - - + + - - - @@ -813,7 +836,6 @@ - @@ -833,7 +855,6 @@ - @@ -1291,7 +1312,7 @@ - + @@ -1345,6 +1366,7 @@ + @@ -1362,6 +1384,7 @@ + @@ -1432,9 +1455,9 @@ - - - + + + diff --git a/src/Umbraco.Examine/ContentValueSetBuilder.cs b/src/Umbraco.Examine/ContentValueSetBuilder.cs index 5e0197765d..44cef08813 100644 --- a/src/Umbraco.Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Examine/ContentValueSetBuilder.cs @@ -1,5 +1,6 @@ using Examine; using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -41,7 +42,7 @@ namespace Umbraco.Examine var urlValue = c.GetUrlSegment(_urlSegmentProviders); //Always add invariant urlName var values = new Dictionary> { - {"icon", c.ContentType.Icon.Yield()}, + {"icon", c.ContentType.Icon?.Yield() ?? Enumerable.Empty()}, {UmbracoExamineIndex.PublishedFieldName, new object[] {c.Published ? "y" : "n"}}, //Always add invariant published value {"id", new object[] {c.Id}}, {UmbracoExamineIndex.NodeKeyFieldName, new object[] {c.Key}}, @@ -51,12 +52,12 @@ namespace Umbraco.Examine {"sortOrder", new object[] {c.SortOrder}}, {"createDate", new object[] {c.CreateDate}}, //Always add invariant createDate {"updateDate", new object[] {c.UpdateDate}}, //Always add invariant updateDate - {"nodeName", PublishedValuesOnly //Always add invariant nodeName - ? c.PublishName.Yield() - : c.Name.Yield()}, - {"urlName", urlValue.Yield()}, //Always add invariant urlName - {"path", c.Path.Yield()}, - {"nodeType", new object[] {c.ContentType.Id}}, + {"nodeName", (PublishedValuesOnly //Always add invariant nodeName + ? c.PublishName?.Yield() + : c?.Name.Yield()) ?? Enumerable.Empty()}, + {"urlName", urlValue?.Yield() ?? Enumerable.Empty()}, //Always add invariant urlName + {"path", c.Path?.Yield() ?? Enumerable.Empty()}, + {"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty()}, {"creatorName", (c.GetCreatorProfile(_userService)?.Name ?? "??").Yield() }, {"writerName",(c.GetWriterProfile(_userService)?.Name ?? "??").Yield() }, {"writerID", new object[] {c.WriterId}}, @@ -72,14 +73,14 @@ namespace Umbraco.Examine { var variantUrl = c.GetUrlSegment(_urlSegmentProviders, culture); var lowerCulture = culture.ToLowerInvariant(); - values[$"urlName_{lowerCulture}"] = variantUrl.Yield(); - values[$"nodeName_{lowerCulture}"] = PublishedValuesOnly - ? c.GetPublishName(culture).Yield() - : c.GetCultureName(culture).Yield(); + values[$"urlName_{lowerCulture}"] = variantUrl?.Yield() ?? Enumerable.Empty(); + values[$"nodeName_{lowerCulture}"] = (PublishedValuesOnly + ? c.GetPublishName(culture)?.Yield() + : c.GetCultureName(culture)?.Yield()) ?? Enumerable.Empty(); values[$"{UmbracoExamineIndex.PublishedFieldName}_{lowerCulture}"] = (c.IsCulturePublished(culture) ? "y" : "n").Yield(); - values[$"updateDate_{lowerCulture}"] = PublishedValuesOnly - ? c.GetPublishDate(culture).Yield() - : c.GetUpdateDate(culture).Yield(); + values[$"updateDate_{lowerCulture}"] = (PublishedValuesOnly + ? c.GetPublishDate(culture) + : c.GetUpdateDate(culture))?.Yield() ?? Enumerable.Empty(); } } diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 19171ac6b1..43a3ccc196 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -28,6 +28,8 @@ namespace Umbraco.Examine /// internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled); + //TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression + /// /// Returns all index fields that are culture specific (suffixed) /// diff --git a/src/Umbraco.Examine/MediaValueSetBuilder.cs b/src/Umbraco.Examine/MediaValueSetBuilder.cs index 23d0414d5d..3839d008b3 100644 --- a/src/Umbraco.Examine/MediaValueSetBuilder.cs +++ b/src/Umbraco.Examine/MediaValueSetBuilder.cs @@ -1,5 +1,6 @@ using Examine; using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -30,7 +31,7 @@ namespace Umbraco.Examine var urlValue = m.GetUrlSegment(_urlSegmentProviders); var values = new Dictionary> { - {"icon", m.ContentType.Icon.Yield()}, + {"icon", m.ContentType.Icon?.Yield() ?? Enumerable.Empty()}, {"id", new object[] {m.Id}}, {UmbracoExamineIndex.NodeKeyFieldName, new object[] {m.Key}}, {"parentID", new object[] {m.Level > 1 ? m.ParentId : -1}}, @@ -39,11 +40,11 @@ namespace Umbraco.Examine {"sortOrder", new object[] {m.SortOrder}}, {"createDate", new object[] {m.CreateDate}}, {"updateDate", new object[] {m.UpdateDate}}, - {"nodeName", m.Name.Yield()}, - {"urlName", urlValue.Yield()}, - {"path", m.Path.Yield()}, - {"nodeType", new object[] {m.ContentType.Id}}, - {"creatorName", m.GetCreatorProfile(_userService).Name.Yield()} + {"nodeName", m.Name?.Yield() ?? Enumerable.Empty()}, + {"urlName", urlValue?.Yield() ?? Enumerable.Empty()}, + {"path", m.Path?.Yield() ?? Enumerable.Empty()}, + {"nodeType", m.ContentType.Id.ToString().Yield() }, + {"creatorName", (m.GetCreatorProfile(_userService)?.Name ?? "??").Yield()} }; foreach (var property in m.Properties) diff --git a/src/Umbraco.Examine/MemberValueSetBuilder.cs b/src/Umbraco.Examine/MemberValueSetBuilder.cs index d9f0b7806d..06fe78105d 100644 --- a/src/Umbraco.Examine/MemberValueSetBuilder.cs +++ b/src/Umbraco.Examine/MemberValueSetBuilder.cs @@ -1,5 +1,6 @@ using Examine; using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -21,7 +22,7 @@ namespace Umbraco.Examine { var values = new Dictionary> { - {"icon", m.ContentType.Icon.Yield()}, + {"icon", m.ContentType.Icon?.Yield() ?? Enumerable.Empty()}, {"id", new object[] {m.Id}}, {UmbracoExamineIndex.NodeKeyFieldName, new object[] {m.Key}}, {"parentID", new object[] {m.Level > 1 ? m.ParentId : -1}}, @@ -30,11 +31,11 @@ namespace Umbraco.Examine {"sortOrder", new object[] {m.SortOrder}}, {"createDate", new object[] {m.CreateDate}}, {"updateDate", new object[] {m.UpdateDate}}, - {"nodeName", m.Name.Yield()}, - {"path", m.Path.Yield()}, - {"nodeType", new object[] {m.ContentType.Id}}, - {"loginName", m.Username.Yield()}, - {"email", m.Email.Yield()}, + {"nodeName", m.Name?.Yield() ?? Enumerable.Empty()}, + {"path", m.Path?.Yield() ?? Enumerable.Empty()}, + {"nodeType", m.ContentType.Id.ToString().Yield() }, + {"loginName", m.Username?.Yield() ?? Enumerable.Empty()}, + {"email", m.Email?.Yield() ?? Enumerable.Empty()}, }; foreach (var property in m.Properties) diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 1320f3b0d2..05b209f927 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,8 +48,8 @@ - - + + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 4c5bb57aa2..49de625450 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -4,15 +4,14 @@ - - - - - - - - - + + + + + + + + @@ -65,23 +64,23 @@ - + - + - + - + - - + + @@ -89,7 +88,7 @@ - + @@ -101,11 +100,11 @@ - + - + diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index 11e2c56873..13fabe5ca2 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -17,10 +17,7 @@ namespace Umbraco.Tests.Cache { private DeepCloneAppCache _provider; - protected override int GetTotalItemCount - { - get { return HttpRuntime.Cache.Count; } - } + protected override int GetTotalItemCount => HttpRuntime.Cache.Count; public override void Setup() { @@ -28,15 +25,9 @@ namespace Umbraco.Tests.Cache _provider = new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)); } - internal override IAppCache AppCache - { - get { return _provider; } - } + internal override IAppCache AppCache => _provider; - internal override IAppPolicyCache AppPolicyCache - { - get { return _provider; } - } + internal override IAppPolicyCache AppPolicyCache => _provider; [Test] public void Clones_List() @@ -101,18 +92,11 @@ namespace Umbraco.Tests.Cache CloneId = Guid.NewGuid(); } - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - } - private string _name; public string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.WriterSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } public Guid CloneId { get; set; } diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index 68b666632c..650cc64720 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -5,7 +5,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Sync; diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index e9448bd0fc..6e8835b9a5 100644 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -3,13 +3,15 @@ using System.Linq; using System.Threading; using Moq; using NUnit.Framework; -using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Tests.Testing; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -149,8 +151,18 @@ namespace Umbraco.Tests.Cache }; + var umbracoContextFactory = new UmbracoContextFactory( + new TestUmbracoContextAccessor(), + Mock.Of(), + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + TestObjects.GetGlobalSettings(), + Enumerable.Empty(), + Mock.Of()); + // just assert it does not throw - var refreshers = new DistributedCacheBinder(null, null); + var refreshers = new DistributedCacheBinder(null, umbracoContextFactory, null); refreshers.HandleEvents(definitions); } } diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index 87ec4215ac..2a6739df38 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -2,6 +2,7 @@ using System.Xml; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 390bb018da..c026e5a157 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -4,7 +4,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Components; +using Umbraco.Core.Compose; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -12,6 +12,8 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; +[assembly:DisableComposer(typeof(Umbraco.Tests.Components.ComponentTests.Composer26))] + namespace Umbraco.Tests.Components { [TestFixture] @@ -104,12 +106,12 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = TypeArray(); - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run // => reorder components accordingly - components.Compose(); + composers.Compose(); AssertTypeArray(TypeArray(), Composed); } @@ -120,11 +122,11 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); // 21 is required by 20 // => reorder components accordingly - components.Compose(); + composers.Compose(); AssertTypeArray(TypeArray(), Composed); } @@ -135,13 +137,13 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); // i23 requires 22 // 24, 25 implement i23 // 25 required by i23 // => reorder components accordingly - components.Compose(); + composers.Compose(); AssertTypeArray(TypeArray(), Composed); } @@ -152,14 +154,14 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); try { // 2 is Core and requires 4 // 4 is missing // => throw - components.Compose(); + composers.Compose(); Assert.Fail("Expected exception."); } catch (Exception e) @@ -175,13 +177,13 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 13 is required by 1 // 1 is missing // => reorder components accordingly - components.Compose(); + composers.Compose(); AssertTypeArray(TypeArray(), Composed); } @@ -200,19 +202,21 @@ namespace Umbraco.Tests.Components { if (type == typeof(Composer1)) return new Composer1(); if (type == typeof(Composer5)) return new Composer5(); + if (type == typeof(Composer5a)) return new Composer5a(); if (type == typeof(Component5)) return new Component5(new SomeResource()); + if (type == typeof(Component5a)) return new Component5a(); if (type == typeof(IProfilingLogger)) return new ProfilingLogger(Mock.Of(), Mock.Of()); throw new NotSupportedException(type.FullName); }); }); var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var types = new[] { typeof(Composer1), typeof(Composer5) }; + var types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; var composers = new Composers(composition, types, Mock.Of()); Assert.IsEmpty(Composed); composers.Compose(); - AssertTypeArray(TypeArray(), Composed); + AssertTypeArray(TypeArray(), Composed); var builder = composition.WithCollectionBuilder(); builder.RegisterWith(register); @@ -220,11 +224,11 @@ namespace Umbraco.Tests.Components Assert.IsEmpty(Initialized); components.Initialize(); - AssertTypeArray(TypeArray(), Initialized); + AssertTypeArray(TypeArray(), Initialized); Assert.IsEmpty(Terminated); components.Terminate(); - AssertTypeArray(TypeArray(), Terminated); + AssertTypeArray(TypeArray(), Terminated); } [Test] @@ -234,9 +238,9 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - components.Compose(); + composers.Compose(); Assert.AreEqual(2, Composed.Count); Assert.AreEqual(typeof(Composer6), Composed[0]); Assert.AreEqual(typeof(Composer8), Composed[1]); @@ -249,9 +253,9 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - components.Compose(); + composers.Compose(); Assert.AreEqual(2, Composed.Count); Assert.AreEqual(typeof(Composer4), Composed[0]); Assert.AreEqual(typeof(Composer2), Composed[1]); @@ -285,26 +289,26 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer10) }; - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - components.Compose(); + composers.Compose(); Assert.AreEqual(1, Composed.Count); Assert.AreEqual(typeof(Composer10), Composed[0]); types = new[] { typeof(Composer11) }; - components = new Core.Components.Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - Assert.Throws(() => components.Compose()); + Assert.Throws(() => composers.Compose()); types = new[] { typeof(Composer2) }; - components = new Core.Components.Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - Assert.Throws(() => components.Compose()); + Assert.Throws(() => composers.Compose()); types = new[] { typeof(Composer12) }; - components = new Core.Components.Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - components.Compose(); + composers.Compose(); Assert.AreEqual(1, Composed.Count); Assert.AreEqual(typeof(Composer12), Composed[0]); } @@ -316,15 +320,36 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - components.Compose(); + composers.Compose(); Assert.AreEqual(2, Composed.Count); Assert.AreEqual(typeof(Composer6), Composed[0]); Assert.AreEqual(typeof(Composer8), Composed[1]); } - #region Components + [Test] + public void AttributesPriorities() + { + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + + var types = new[] { typeof(Composer26) }; // 26 disabled by assembly attribute + var composers = new Composers(composition, types, Mock.Of()); + Composed.Clear(); + composers.Compose(); + Assert.AreEqual(0, Composed.Count); // 26 gone + + types = new[] { typeof(Composer26), typeof(Composer27) }; // 26 disabled by assembly attribute, enabled by 27 + composers = new Composers(composition, types, Mock.Of()); + Composed.Clear(); + composers.Compose(); + Assert.AreEqual(2, Composed.Count); // both + Assert.AreEqual(typeof(Composer26), Composed[0]); + Assert.AreEqual(typeof(Composer27), Composed[1]); + } + + #region Compothings public class TestComposerBase : IComposer { @@ -356,6 +381,16 @@ namespace Umbraco.Tests.Components } } + [ComposeAfter(typeof(Composer5))] + public class Composer5a : TestComposerBase + { + public override void Compose(Composition composition) + { + base.Compose(composition); + composition.Components().Append(); + } + } + public class TestComponentBase : IComponent { public virtual void Initialize() @@ -379,6 +414,9 @@ namespace Umbraco.Tests.Components } } + public class Component5a : TestComponentBase + { } + [Disable] public class Composer6 : TestComposerBase { } @@ -440,6 +478,14 @@ namespace Umbraco.Tests.Components public class Composer25 : TestComposerBase, IComposer23 { } + // disabled by assembly attribute + public class Composer26 : TestComposerBase + { } + + [Enable(typeof(Composer26))] + public class Composer27 : TestComposerBase + { } + #endregion #region TypeArray diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index 8ea13fc920..ec757e09f0 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -5,7 +5,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Composing; using Umbraco.Core; -using Umbraco.Core.Components; using Umbraco.Core.Logging; using Umbraco.Tests.Components; @@ -262,6 +261,40 @@ namespace Umbraco.Tests.Composing AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); } + [Test] + public void CanInsertIntoBuilderAfter() + { + var builder = _composition.WithCollectionBuilder() + .Append() + .Append() + .InsertAfter(); + + Assert.IsTrue(builder.Has()); + Assert.IsTrue(builder.Has()); + Assert.IsTrue(builder.Has()); + + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); + AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); + } + + [Test] + public void CanInsertIntoBuilderAfterLast() + { + var builder = _composition.WithCollectionBuilder() + .Append() + .Append() + .InsertAfter(); + + Assert.IsTrue(builder.Has()); + Assert.IsTrue(builder.Has()); + Assert.IsTrue(builder.Has()); + + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); + AssertCollection(col, typeof(Resolved1), typeof(Resolved2), typeof(Resolved3)); + } + [Test] public void CannotInsertIntoBuilderBeforeOnceCollectionIsCreated() { @@ -314,7 +347,7 @@ namespace Umbraco.Tests.Composing var factory = _composition.CreateFactory(); using (factory.BeginScope()) - { + { var col1 = factory.GetInstance(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index 407d953509..ef364afd38 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Tests.TestHelpers; @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - TypeLoader = new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, ProfilingLogger, detectChanges: false) + TypeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false) { AssembliesToScan = AssembliesToScan }; diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index 86226b82e7..f5c1ff9bc7 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; namespace Umbraco.Tests.Composing diff --git a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs index cbabae1a83..3996eba89f 100644 --- a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs @@ -5,7 +5,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs index fe8ca7dadc..07db4be3d3 100644 --- a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs @@ -5,7 +5,6 @@ using System.Xml.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.PackageActions; diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 2354dc8147..b3237dd4ba 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - _typeLoader = new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of())); + _typeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of())); foreach (var file in Directory.GetFiles(IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "TypesCache"))) File.Delete(file); @@ -264,7 +264,7 @@ AnotherContentFinder public void Resolves_Assigned_Mappers() { var foundTypes1 = _typeLoader.GetAssignedMapperTypes(); - Assert.AreEqual(29, foundTypes1.Count()); + Assert.AreEqual(30, foundTypes1.Count()); } [Test] @@ -278,7 +278,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(39, types.Count()); + Assert.AreEqual(38, types.Count()); } /// diff --git a/src/Umbraco.Tests/CoreThings/GuidUtilsTests.cs b/src/Umbraco.Tests/CoreThings/GuidUtilsTests.cs index 5ef8cba356..639d85b4ff 100644 --- a/src/Umbraco.Tests/CoreThings/GuidUtilsTests.cs +++ b/src/Umbraco.Tests/CoreThings/GuidUtilsTests.cs @@ -15,6 +15,14 @@ namespace Umbraco.Tests.CoreThings Assert.AreEqual(GuidUtils.Combine(a, b).ToByteArray(), Combine(a, b)); } + [Test] + public void GuidThingTest() + { + var guid = new Guid("f918382f-2bba-453f-a3e2-1f594016ed3b"); + Assert.AreEqual("f22br4n0fm5fli5c", GuidUtils.ToBase32String(guid, 16)); + Assert.AreEqual("f22br4n0f", GuidUtils.ToBase32String(guid, 9)); + } + // Reference implementation taken from original code. private static byte[] Combine(Guid guid1, Guid guid2) { diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index c700b78c4b..100b39c548 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Deploy; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Serialization; using Umbraco.Tests.TestHelpers; @@ -26,7 +27,7 @@ namespace Umbraco.Tests.CoreThings var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); Current.Factory = container.Object; Udi.ResetUdiTypes(); diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index 1f98fd4443..b21faec586 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -4,16 +4,16 @@ using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Components; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.Composers; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; +using Umbraco.Core.Composing.CompositionExtensions; +using FileSystems = Umbraco.Core.IO.FileSystems; namespace Umbraco.Tests.IO { @@ -33,7 +33,7 @@ namespace Umbraco.Tests.IO composition.Register(_ => Mock.Of()); composition.Register(_ => Mock.Of()); composition.Register(_ => Mock.Of()); - composition.RegisterUnique(); + composition.RegisterUnique(); composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); @@ -112,9 +112,19 @@ namespace Umbraco.Tests.IO fs.DeleteMediaFiles(new[] { virtPath }); Assert.IsFalse(File.Exists(physPath)); - // ~/media/1234 is gone - physPath = Path.GetDirectoryName(physPath); - Assert.IsFalse(Directory.Exists(physPath)); + var scheme = Current.Factory.GetInstance(); + if (scheme is UniqueMediaPathScheme) + { + // ~/media/1234 is *not* gone + physPath = Path.GetDirectoryName(physPath); + Assert.IsTrue(Directory.Exists(physPath)); + } + else + { + // ~/media/1234 is gone + physPath = Path.GetDirectoryName(physPath); + Assert.IsFalse(Directory.Exists(physPath)); + } // ~/media exists physPath = Path.GetDirectoryName(physPath); diff --git a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs index 91e764c184..ab9b2cf73d 100644 --- a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Threading; using NUnit.Framework; @@ -44,6 +42,34 @@ namespace Umbraco.Tests.IO return "/Media/" + path; } + private string Repeat(string pattern, int count) + { + var text = new StringBuilder(); + for (var i = 0; i < count; i++) + text.Append(pattern); + return text.ToString(); + } + + [Test] + public void SaveFileTest() + { + var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"); + + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) + _fileSystem.AddFile("sub/f3.txt", ms); + + Assert.IsTrue(File.Exists(Path.Combine(basePath, "sub/f3.txt"))); + + var path = Repeat("bah/bah/", 50); + Assert.Less(260, path.Length); + + Assert.Throws(() => + { + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) + _fileSystem.AddFile(path + "f3.txt", ms); + }); + } + [Test] public void GetFullPathTest() { @@ -60,14 +86,31 @@ namespace Umbraco.Tests.IO // - does properly normalize separators // - does throw on invalid paths + // works var path = _fileSystem.GetFullPath("foo.tmp"); Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path); + // a very long relative path, which ends up being a short path, works + path = Repeat("bah/../", 50); + Assert.Less(260, path.Length); + path = _fileSystem.GetFullPath(path + "foo.tmp"); + Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path); + + // works too path = _fileSystem.GetFullPath("foo/bar.tmp"); Assert.AreEqual(Path.Combine(basePath, @"foo\bar.tmp"), path); // that path is invalid as it would be outside the root directory Assert.Throws(() => _fileSystem.GetFullPath("../../foo.tmp")); + + // a very long path, which ends up being very long, works + path = Repeat("bah/bah/", 50); + Assert.Less(260, path.Length); + Assert.Throws(() => + { + path = _fileSystem.GetFullPath(path + "foo.tmp"); + Assert.Less(260, path.Length); // gets a >260 path and it's fine (but Windows will not like it) + }); } } } diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 2244f9085d..31b00e5cf8 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -405,10 +405,10 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f1.txt", ms); Assert.IsTrue(phy.FileExists("sub/f1.txt")); - Guid id; + string id; // explicit shadow without scope does not work - sw.Shadow(id = Guid.NewGuid()); + sw.Shadow(id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); @@ -419,7 +419,7 @@ namespace Umbraco.Tests.IO // shadow with scope but no complete does not complete scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); @@ -428,10 +428,11 @@ namespace Umbraco.Tests.IO Assert.AreEqual(1, dirs.Length); Assert.AreEqual((shadowfs + "/" + id).Replace('\\', '/'), dirs[0].Replace('\\', '/')); dirs = Directory.GetDirectories(dirs[0]); - var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/typed")); + var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/x")); Assert.IsNotNull(typedDir); dirs = Directory.GetDirectories(typedDir); - var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/Umbraco.Tests.IO.ShadowFileSystemTests+FS")); // this is where files go + var suid = fileSystems.Paths[typeof(FS)]; + var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/" + suid)); // this is where files go Assert.IsNotNull(scopedDir); scope.Dispose(); scopedFileSystems = false; @@ -440,7 +441,7 @@ namespace Umbraco.Tests.IO // shadow with scope and complete does complete scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f4.txt", ms); @@ -456,7 +457,7 @@ namespace Umbraco.Tests.IO // test scope for "another thread" scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f5.txt", ms); @@ -473,7 +474,7 @@ namespace Umbraco.Tests.IO scope.Dispose(); scopedFileSystems = false; Assert.IsTrue(phy.FileExists("sub/f5.txt")); - Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); + TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(shadowfs + "/" + id))); } [Test] @@ -498,10 +499,10 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f1.txt", ms); Assert.IsTrue(phy.FileExists("sub/f1.txt")); - Guid id; + string id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); @@ -551,10 +552,10 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f1.txt", ms); Assert.IsTrue(phy.FileExists("sub/f1.txt")); - Guid id; + string id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 7b22d282f0..c3708dd8f3 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Sync; using Umbraco.Tests.Services; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; +using Umbraco.Web; using Umbraco.Web.Cache; using static Umbraco.Tests.Cache.DistributedCache.DistributedCacheTests; @@ -32,7 +33,7 @@ namespace Umbraco.Tests.Integration { base.SetUp(); - _h1 = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _h1 = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _h1.BindEvents(true); _events = new List(); diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs similarity index 81% rename from src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs rename to src/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs index 5929f5cb81..2f2c1e787a 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs @@ -1,9 +1,10 @@ using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; -namespace Umbraco.Core.Persistence.Dtos +namespace Umbraco.Tests.LegacyXmlPublishedCache { - [TableName(Constants.DatabaseSchema.Tables.NodeXml)] + [TableName("cmsContentXml")] [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class ContentXmlDto @@ -20,4 +21,4 @@ namespace Umbraco.Core.Persistence.Dtos [Column("rv")] public long Rv { get; set; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs similarity index 81% rename from src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs rename to src/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs index 40cc50b00a..4fcbef820a 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs @@ -1,9 +1,10 @@ using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; -namespace Umbraco.Core.Persistence.Dtos +namespace Umbraco.Tests.LegacyXmlPublishedCache { - [TableName(Constants.DatabaseSchema.Tables.NodePreviewXml)] + [TableName("cmsPreviewXml")] [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class PreviewXmlDto @@ -20,4 +21,4 @@ namespace Umbraco.Core.Persistence.Dtos [Column("rv")] public long Rv { get; set; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 51c6668913..5fc0d628c9 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -546,11 +546,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _contentTypeCache.Get(PublishedItemType.Content, alias); } - public override IEnumerable GetByContentType(PublishedContentType contentType) - { - throw new NotImplementedException(); - } - #endregion } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs index 8cfc06c501..71490465d0 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs @@ -70,7 +70,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache /// /// /// - internal PublishedMediaCache(IMediaService mediaService, IUserService userService, ISearcher searchProvider, IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer) + internal PublishedMediaCache(IMediaService mediaService, IUserService userService, ISearcher searchProvider, IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer, IUmbracoContextAccessor umbracoContextAccessor) : base(false) { _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); @@ -79,6 +79,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _appCache = appCache; _contentTypeCache = contentTypeCache; _entitySerializer = entitySerializer; + _umbracoContextAccessor = umbracoContextAccessor; } static PublishedMediaCache() @@ -587,7 +588,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { int id; if (int.TryParse(itemm.GetAttribute("id", ""), out id) == false) - continue; // wtf? + continue; // uh? var captured = itemm; var cacheValues = GetCacheValues(id, idd => ConvertFromXPathNavigator(captured)); mediaList.Add(CreateFromCacheValues(cacheValues)); @@ -620,7 +621,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override IEnumerable GetByContentType(PublishedContentType contentType) { - throw new NotImplementedException(); + throw new NotSupportedException(); } #endregion @@ -645,7 +646,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private static void InitializeCacheConfig() { - var value = ConfigurationManager.AppSettings["Umbraco.PublishedMediaCache.Seconds"]; + var value = ConfigurationManager.AppSettings[Constants.AppSettings.PublishedMediaCacheSeconds]; int seconds; if (int.TryParse(value, out seconds) == false) seconds = PublishedMediaCacheTimespanSeconds; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshot.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshot.cs index bfef187371..c7be2e6be0 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshot.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshot.cs @@ -51,10 +51,13 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return new ForcedPreviewObject(); } - private class ForcedPreviewObject : DisposableObject + private class ForcedPreviewObject : DisposableObjectSlim { protected override void DisposeResources() { } } + + public void Dispose() + { } } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/UmbracoContextCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/UmbracoContextCache.cs index fc6dbe3f30..5d48e9eae3 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/UmbracoContextCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/UmbracoContextCache.cs @@ -13,7 +13,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { get { - var umbracoContext = UmbracoContext.Current; + var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext; // will get or create a value // a ConditionalWeakTable is thread-safe diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs index 6dec9f2448..3b675c2f07 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs @@ -12,7 +12,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs index 75e2c66a61..35981f5368 100644 --- a/src/Umbraco.Tests/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using System; using System.IO; using System.Linq; @@ -47,7 +48,8 @@ namespace Umbraco.Tests.Logging File.Copy(exampleLogfilePath, _newLogfilePath, true); File.Copy(exampleSearchfilePath, _newSearchfilePath, true); - _logViewer = new JsonLogViewer(logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath); + var logger = Mock.Of(); + _logViewer = new JsonLogViewer(logger, logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath); } [OneTimeTearDown] diff --git a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs index 016eb4113a..1e02f562e3 100644 --- a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Manifest var contentType = Mock.Of(); Mock.Get(contentType).Setup(x => x.Alias).Returns("type1"); var content = Mock.Of(); - Mock.Get(content).Setup(x => x.ContentType).Returns(contentType); + Mock.Get(content).Setup(x => x.ContentType).Returns(new SimpleContentType(contentType)); var group1 = Mock.Of(); Mock.Get(group1).Setup(x => x.Alias).Returns("group1"); diff --git a/src/Umbraco.Tests/Migrations/MigrationTests.cs b/src/Umbraco.Tests/Migrations/MigrationTests.cs index 8e84f88265..6b2d21e4a5 100644 --- a/src/Umbraco.Tests/Migrations/MigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationTests.cs @@ -16,32 +16,6 @@ namespace Umbraco.Tests.Migrations [TestFixture] public class MigrationTests { - public class TestUpgraderWithPostMigrations : Upgrader - { - private PostMigrationCollection _postMigrations; - - public TestUpgraderWithPostMigrations(MigrationPlan plan) - : base(plan) - { } - - public void Execute(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, ILogger logger, PostMigrationCollection postMigrations) - { - _postMigrations = postMigrations; - Execute(scopeProvider, migrationBuilder, keyValueService, logger); - } - - public override void AfterMigrations(IScope scope, ILogger logger) - { - // run post-migrations - var originVersion = new SemVersion(0); - var targetVersion = new SemVersion(0); - - // run post-migrations - foreach (var postMigration in _postMigrations) - postMigration.Execute(Name, scope, originVersion, targetVersion, logger); - } - } - public class TestScopeProvider : IScopeProvider { private readonly IScope _scope; diff --git a/src/Umbraco.Tests/Migrations/PostMigrationTests.cs b/src/Umbraco.Tests/Migrations/PostMigrationTests.cs index 95f1f8afac..3e96ddb435 100644 --- a/src/Umbraco.Tests/Migrations/PostMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/PostMigrationTests.cs @@ -2,10 +2,9 @@ using Moq; using NPoco; using NUnit.Framework; -using Semver; -using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Migrations; +using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; @@ -18,15 +17,10 @@ namespace Umbraco.Tests.Migrations public class PostMigrationTests { [Test] - public void Executes_For_Any_Product_Name_When_Not_Specified() + public void ExecutesPlanPostMigration() { var logger = Mock.Of(); - var changed1 = new Args { CountExecuted = 0 }; - var post1 = new TestPostMigration(changed1); - - var posts = new PostMigrationCollection(new [] { post1 }); - var builder = Mock.Of(); Mock.Get(builder) .Setup(x => x.Build(It.IsAny(), It.IsAny())) @@ -34,8 +28,10 @@ namespace Umbraco.Tests.Migrations { switch (t.Name) { - case "NoopMigration": + case nameof(NoopMigration): return new NoopMigration(); + case nameof(TestPostMigration): + return new TestPostMigration(); default: throw new NotSupportedException(); } @@ -50,26 +46,23 @@ namespace Umbraco.Tests.Migrations var sqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, Mock.Of()); var scopeProvider = new MigrationTests.TestScopeProvider(scope) { SqlContext = sqlContext }; - var u1 = new MigrationTests.TestUpgraderWithPostMigrations( - new MigrationPlan("Test").From(string.Empty).To("done")); - u1.Execute(scopeProvider, builder, Mock.Of(), logger, posts); + var plan = new MigrationPlan("Test") + .From(string.Empty).To("done"); - Assert.AreEqual(1, changed1.CountExecuted); + plan.AddPostMigration(); + TestPostMigration.MigrateCount = 0; + + var upgrader = new Upgrader(plan); + upgrader.Execute(scopeProvider, builder, Mock.Of(), logger); + + Assert.AreEqual(1, TestPostMigration.MigrateCount); } [Test] - public void Executes_Only_For_Specified_Product_Name() + public void MigrationCanAddPostMigration() { var logger = Mock.Of(); - var changed1 = new Args { CountExecuted = 0}; - var post1 = new TestPostMigration("Test1", changed1); - - var changed2 = new Args { CountExecuted = 0 }; - var post2 = new TestPostMigration("Test2", changed2); - - var posts = new PostMigrationCollection(new [] { post1, post2 }); - var builder = Mock.Of(); Mock.Get(builder) .Setup(x => x.Build(It.IsAny(), It.IsAny())) @@ -77,8 +70,12 @@ namespace Umbraco.Tests.Migrations { switch (t.Name) { - case "NoopMigration": + case nameof(NoopMigration): return new NoopMigration(); + case nameof(TestMigration): + return new TestMigration(c); + case nameof(TestPostMigration): + return new TestPostMigration(); default: throw new NotSupportedException(); } @@ -93,52 +90,44 @@ namespace Umbraco.Tests.Migrations var sqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, Mock.Of()); var scopeProvider = new MigrationTests.TestScopeProvider(scope) { SqlContext = sqlContext }; - var u1 = new MigrationTests.TestUpgraderWithPostMigrations( - new MigrationPlan("Test1").From(string.Empty).To("done")); - u1.Execute(scopeProvider, builder, Mock.Of(), logger, posts); + var plan = new MigrationPlan("Test") + .From(string.Empty).To("done"); - Assert.AreEqual(1, changed1.CountExecuted); - Assert.AreEqual(0, changed2.CountExecuted); + TestMigration.MigrateCount = 0; + TestPostMigration.MigrateCount = 0; - var u2 = new MigrationTests.TestUpgraderWithPostMigrations( - new MigrationPlan("Test2").From(string.Empty).To("done")); - u2.Execute(scopeProvider, builder, Mock.Of(), logger, posts); + new MigrationContext(database, logger); - Assert.AreEqual(1, changed1.CountExecuted); - Assert.AreEqual(1, changed2.CountExecuted); + var upgrader = new Upgrader(plan); + upgrader.Execute(scopeProvider, builder, Mock.Of(), logger); + + Assert.AreEqual(1, TestMigration.MigrateCount); + Assert.AreEqual(1, TestPostMigration.MigrateCount); } - public class Args + public class TestMigration : MigrationBase { - public int CountExecuted { get; set; } + public TestMigration(IMigrationContext context) + : base(context) + { } + + public static int MigrateCount { get; set; } + + public override void Migrate() + { + MigrateCount++; + + Context.AddPostMigration(); + } } - public class TestPostMigration : IPostMigration + public class TestPostMigration : IMigration { - private readonly string _prodName; - private readonly Args _changed; + public static int MigrateCount { get; set; } - // need that one else it breaks IoC - public TestPostMigration() + public void Migrate() { - _changed = new Args(); - } - - public TestPostMigration(Args changed) - { - _changed = changed; - } - - public TestPostMigration(string prodName, Args changed) - { - _prodName = prodName; - _changed = changed; - } - - public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger) - { - if (_prodName.IsNullOrWhiteSpace() == false && name != _prodName) return; - _changed.CountExecuted++; + MigrateCount++; } } } diff --git a/src/Umbraco.Tests/Models/ContentExtensionsTests.cs b/src/Umbraco.Tests/Models/ContentExtensionsTests.cs index 5c95f1ead5..42d7f0e01b 100644 --- a/src/Umbraco.Tests/Models/ContentExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/ContentExtensionsTests.cs @@ -1,22 +1,59 @@ using System; using System.Linq; +using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing.CompositionExtensions; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Models { [TestFixture] public class ContentExtensionsTests : UmbracoTestBase { - #region Others + private IContentTypeService _contentTypeService; + + protected override void Compose() + { + base.Compose(); + + Composition.Register(_ => Mock.Of()); + Composition.ComposeFileSystems(); + + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + + // all this is required so we can validate properties... + var editor = new TextboxPropertyEditor(Mock.Of()) { Alias = "test" }; + Composition.Register(_ => new DataEditorCollection(new[] { editor })); + Composition.Register(); + var dataType = Mock.Of(); + Mock.Get(dataType).Setup(x => x.Configuration).Returns(() => new object()); + var dataTypeService = Mock.Of(); + Mock.Get(dataTypeService) + .Setup(x => x.GetDataType(It.IsAny())) + .Returns(() => dataType); + + _contentTypeService = Mock.Of(); + var mediaTypeService = Mock.Of(); + var memberTypeService = Mock.Of(); + Composition.Register(_ => ServiceContext.CreatePartial(dataTypeService: dataTypeService, contentTypeBaseServiceProvider: new ContentTypeBaseServiceProvider(_contentTypeService, mediaTypeService, memberTypeService))); + } [Test] public void DirtyProperty_Reset_Clears_SavedPublishedState() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.PublishedState = PublishedState.Publishing; @@ -30,6 +67,8 @@ namespace Umbraco.Tests.Models public void DirtyProperty_OnlyIfActuallyChanged_Content() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // if you assign a content property with its value it is not dirty @@ -52,6 +91,8 @@ namespace Umbraco.Tests.Models public void DirtyProperty_OnlyIfActuallyChanged_User() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); var prop = content.Properties.First(); @@ -76,6 +117,8 @@ namespace Umbraco.Tests.Models public void DirtyProperty_UpdateDate() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); var prop = content.Properties.First(); @@ -99,6 +142,8 @@ namespace Umbraco.Tests.Models public void DirtyProperty_WasDirty_ContentProperty() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); @@ -126,6 +171,8 @@ namespace Umbraco.Tests.Models public void DirtyProperty_WasDirty_ContentSortOrder() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); @@ -153,6 +200,8 @@ namespace Umbraco.Tests.Models public void DirtyProperty_WasDirty_UserProperty() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); var prop = content.Properties.First(); content.ResetDirtyProperties(false); @@ -178,7 +227,5 @@ namespace Umbraco.Tests.Models //Assert.IsFalse(content.WasDirty()); // not impacted by user properties Assert.IsTrue(content.WasDirty()); // now it is! } - - #endregion } } diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 14f766fba1..05f726893e 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -8,16 +8,19 @@ using Moq; using Umbraco.Core; using NUnit.Framework; using Umbraco.Core.Cache; -using Umbraco.Core.Composing.Composers; +using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Serialization; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Models { @@ -25,6 +28,8 @@ namespace Umbraco.Tests.Models [TestFixture] public class ContentTests : UmbracoTestBase { + private IContentTypeService _contentTypeService; + protected override void Compose() { base.Compose(); @@ -34,18 +39,36 @@ namespace Umbraco.Tests.Models Composition.Register(_ => Mock.Of()); Composition.Register(_ => Mock.Of()); + + // all this is required so we can validate properties... + var editor = new TextboxPropertyEditor(Mock.Of()) { Alias = "test" }; + Composition.Register(_ => new DataEditorCollection(new [] { editor })); + Composition.Register(); + var dataType = Mock.Of(); + Mock.Get(dataType).Setup(x => x.Configuration).Returns(() => new object()); + var dataTypeService = Mock.Of(); + Mock.Get(dataTypeService) + .Setup(x => x.GetDataType(It.IsAny())) + .Returns(() => dataType); + + _contentTypeService = Mock.Of(); + var mediaTypeService = Mock.Of(); + var memberTypeService = Mock.Of(); + Composition.Register(_ => ServiceContext.CreatePartial(dataTypeService: dataTypeService, contentTypeBaseServiceProvider: new ContentTypeBaseServiceProvider(_contentTypeService, mediaTypeService, memberTypeService))); + } [Test] public void Variant_Culture_Names_Track_Dirty_Changes() { var contentType = new ContentType(-1) { Alias = "contentType" }; + contentType.Variations = ContentVariation.Culture; + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; const string langFr = "fr-FR"; - contentType.Variations = ContentVariation.Culture; - Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); //hasn't been changed Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date @@ -75,6 +98,8 @@ namespace Umbraco.Tests.Models contentType.Variations = ContentVariation.Culture; + content.ChangeContentType(contentType); + Assert.IsFalse(content.IsPropertyDirty("PublishCultureInfos")); //hasn't been changed Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date @@ -106,6 +131,7 @@ namespace Umbraco.Tests.Models //ensure that nothing is marked as dirty contentType.ResetDirtyProperties(false); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); var content = MockedContent.CreateSimpleContent(contentType); @@ -121,6 +147,8 @@ namespace Umbraco.Tests.Models public void All_Dirty_Properties_Get_Reset() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(false); @@ -137,6 +165,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -150,6 +180,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -166,6 +198,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -182,6 +216,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.Id = 10; content.Key = new Guid("29181B97-CB8F-403F-86DE-5FEB497F4800"); @@ -261,6 +297,8 @@ namespace Umbraco.Tests.Models var contentType = MockedContentTypes.CreateTextPageContentType(); contentType.Id = 99; contentType.Variations = ContentVariation.Culture; + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.SetCultureName("Hello", "en-US"); @@ -301,20 +339,7 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone, content); Assert.AreEqual(clone.Id, content.Id); Assert.AreEqual(clone.VersionId, content.VersionId); - Assert.AreNotSame(clone.ContentType, content.ContentType); Assert.AreEqual(clone.ContentType, content.ContentType); - Assert.AreEqual(clone.ContentType.PropertyGroups.Count, content.ContentType.PropertyGroups.Count); - for (var index = 0; index < content.ContentType.PropertyGroups.Count; index++) - { - Assert.AreNotSame(clone.ContentType.PropertyGroups[index], content.ContentType.PropertyGroups[index]); - Assert.AreEqual(clone.ContentType.PropertyGroups[index], content.ContentType.PropertyGroups[index]); - } - Assert.AreEqual(clone.ContentType.PropertyTypes.Count(), content.ContentType.PropertyTypes.Count()); - for (var index = 0; index < content.ContentType.PropertyTypes.Count(); index++) - { - Assert.AreNotSame(clone.ContentType.PropertyTypes.ElementAt(index), content.ContentType.PropertyTypes.ElementAt(index)); - Assert.AreEqual(clone.ContentType.PropertyTypes.ElementAt(index), content.ContentType.PropertyTypes.ElementAt(index)); - } Assert.AreEqual(clone.ContentTypeId, content.ContentTypeId); Assert.AreEqual(clone.CreateDate, content.CreateDate); Assert.AreEqual(clone.CreatorId, content.CreatorId); @@ -383,6 +408,8 @@ namespace Umbraco.Tests.Models var contentType = MockedContentTypes.CreateTextPageContentType(); contentType.Id = 99; contentType.Variations = ContentVariation.Culture; + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.SetCultureName("Hello", "en-US"); @@ -407,8 +434,6 @@ namespace Umbraco.Tests.Models content.Trashed = true; content.UpdateDate = DateTime.Now; content.WriterId = 23; - - content.ContentType.UpdateDate = DateTime.Now; //update a child object // Act content.ResetDirtyProperties(); @@ -435,19 +460,17 @@ namespace Umbraco.Tests.Models Assert.IsTrue(content.WasPropertyDirty("CultureInfos")); foreach(var culture in content.CultureInfos) { - Assert.IsTrue(culture.Value.WasDirty()); - Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); - Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + Assert.IsTrue(culture.WasDirty()); + Assert.IsTrue(culture.WasPropertyDirty("Name")); + Assert.IsTrue(culture.WasPropertyDirty("Date")); } Assert.IsTrue(content.WasPropertyDirty("PublishCultureInfos")); foreach (var culture in content.PublishCultureInfos) { - Assert.IsTrue(culture.Value.WasDirty()); - Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); - Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + Assert.IsTrue(culture.WasDirty()); + Assert.IsTrue(culture.WasPropertyDirty("Name")); + Assert.IsTrue(culture.WasPropertyDirty("Date")); } - //verify child objects were reset too - Assert.IsTrue(content.ContentType.WasPropertyDirty("UpdateDate")); } [Test] @@ -458,6 +481,8 @@ namespace Umbraco.Tests.Models // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); contentType.Id = 99; + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); var i = 200; foreach (var property in content.Properties) @@ -509,10 +534,12 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act - content.PropertyValues(new {title = "This is the new title"}); + content.PropertyValues(new { title = "This is the new title"}); // Assert Assert.That(content.Properties.Any(), Is.True); @@ -527,6 +554,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -543,14 +572,12 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); - var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act contentType.PropertyGroups.Add(new PropertyGroup(true) { Name = "Test Group", SortOrder = 3 }); // Assert Assert.That(contentType.PropertyGroups.Count, Is.EqualTo(3)); - Assert.That(content.PropertyGroups.Count(), Is.EqualTo(3)); } [Test] @@ -573,7 +600,6 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); - var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act contentType.PropertyGroups["Content"].PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "subtitle") @@ -587,7 +613,6 @@ namespace Umbraco.Tests.Models // Assert Assert.That(contentType.PropertyGroups["Content"].PropertyTypes.Count, Is.EqualTo(3)); - Assert.That(content.PropertyGroups.First(x => x.Name == "Content").PropertyTypes.Count, Is.EqualTo(3)); } [Test] @@ -595,6 +620,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -617,6 +644,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -637,8 +666,6 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Count, Is.EqualTo(5)); - Assert.That(content.PropertyTypes.Count(), Is.EqualTo(5)); - Assert.That(content.PropertyGroups.Count(), Is.EqualTo(3)); Assert.That(content.Properties["subtitle"].GetValue(), Is.EqualTo("Subtitle Test")); Assert.That(content.Properties["title"].GetValue(), Is.EqualTo("Textpage textpage")); } @@ -648,6 +675,8 @@ namespace Umbraco.Tests.Models { // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act - note that the PropertyType's properties like SortOrder is not updated through the Content object @@ -669,6 +698,8 @@ namespace Umbraco.Tests.Models // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); var simpleContentType = MockedContentTypes.CreateSimpleContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -676,8 +707,6 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Contains("author"), Is.True); - Assert.That(content.PropertyGroups.Count(), Is.EqualTo(1)); - Assert.That(content.PropertyTypes.Count(), Is.EqualTo(3)); //Note: There was 4 properties, after changing ContentType 1 has been added (no properties are deleted) Assert.That(content.Properties.Count, Is.EqualTo(5)); } @@ -688,6 +717,8 @@ namespace Umbraco.Tests.Models // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); var simpleContentType = MockedContentTypes.CreateSimpleContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -705,6 +736,8 @@ namespace Umbraco.Tests.Models // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); var simpleContentType = MockedContentTypes.CreateSimpleContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -719,26 +752,32 @@ namespace Umbraco.Tests.Models } [Test] + [Ignore("Need to reimplement this logic for v8")] public void Can_Change_ContentType_On_Content_And_Clear_Old_PropertyTypes() { - // Arrange - var contentType = MockedContentTypes.CreateTextPageContentType(); - var simpleContentType = MockedContentTypes.CreateSimpleContentType(); - var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + throw new NotImplementedException(); + //Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - // Act - content.ChangeContentType(simpleContentType, true); + //// Arrange + //var contentType = MockedContentTypes.CreateTextPageContentType(); + //var simpleContentType = MockedContentTypes.CreateSimpleContentType(); + //var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); - // Assert - Assert.That(content.Properties.Contains("author"), Is.True); - Assert.That(content.Properties.Contains("keywords"), Is.False); - Assert.That(content.Properties.Contains("description"), Is.False); + //// Act + //content.ChangeContentType(simpleContentType, true); + + //// Assert + //Assert.That(content.Properties.Contains("author"), Is.True); + //Assert.That(content.Properties.Contains("keywords"), Is.False); + //Assert.That(content.Properties.Contains("description"), Is.False); } [Test] public void Can_Verify_Content_Is_Published() { var contentType = MockedContentTypes.CreateTextPageContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(); @@ -808,6 +847,8 @@ namespace Umbraco.Tests.Models // Arrange var contentType = MockedContentTypes.CreateTextPageContentType(); contentType.ResetDirtyProperties(); //reset + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateTextpageContent(contentType, "test", -1); content.ResetDirtyProperties(); diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs index 59c514f654..18bceaae49 100644 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs +++ b/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs @@ -121,16 +121,16 @@ namespace Umbraco.Tests.Models.Mapping break; case 1: map - .ForMember(dest => dest.ValueString, opt => opt.ResolveUsing(src => src.ValueString)); + .ForMember(dest => dest.ValueString, opt => opt.MapFrom(src => src.ValueString)); break; case 2: map - .ForMember(dest => dest.ValueString, opt => opt.ResolveUsing()); + .ForMember(dest => dest.ValueString, opt => opt.MapFrom()); break; case 3: // in most cases that should be perfectly enough? map - .ForMember(dest => dest.ValueString, opt => opt.ResolveUsing(source => "!!" + source.ValueString + "!!")); + .ForMember(dest => dest.ValueString, opt => opt.MapFrom(source => "!!" + source.ValueString + "!!")); break; default: throw new ArgumentOutOfRangeException(nameof(ver)); diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 0de416ed72..2e6cfaae89 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Models.Mapping Mapper.Initialize(configuration => { //initialize our content type mapper - var profile1 = new ContentTypeMapperProfile(_editorsMock.Object, _dataTypeService.Object, _fileService.Object, _contentTypeService.Object, Mock.Of()); + var profile1 = new ContentTypeMapperProfile(_editorsMock.Object, _dataTypeService.Object, _fileService.Object, _contentTypeService.Object, Mock.Of(), Mock.Of()); configuration.AddProfile(profile1); var profile2 = new EntityMapperProfile(); configuration.AddProfile(profile2); @@ -311,6 +311,8 @@ namespace Umbraco.Tests.Models.Mapping public void IMemberType_To_MemberTypeDisplay() { //Arrange + _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) + .Returns(new DataType(new VoidEditor(Mock.Of()))); // setup the mocks to return the data we want to test against... @@ -369,6 +371,8 @@ namespace Umbraco.Tests.Models.Mapping public void IMediaType_To_MediaTypeDisplay() { //Arrange + _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) + .Returns(new DataType(new VoidEditor(Mock.Of()))); // setup the mocks to return the data we want to test against... @@ -422,6 +426,8 @@ namespace Umbraco.Tests.Models.Mapping public void IContentType_To_ContentTypeDisplay() { //Arrange + _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) + .Returns(new DataType(new VoidEditor(Mock.Of()))); // setup the mocks to return the data we want to test against... @@ -687,6 +693,8 @@ namespace Umbraco.Tests.Models.Mapping public void IMediaTypeComposition_To_MediaTypeDisplay() { //Arrange + _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) + .Returns(new DataType(new VoidEditor(Mock.Of()))); // setup the mocks to return the data we want to test against... @@ -778,6 +786,8 @@ namespace Umbraco.Tests.Models.Mapping public void IContentTypeComposition_To_ContentTypeDisplay() { //Arrange + _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) + .Returns(new DataType(new VoidEditor(Mock.Of()))); // setup the mocks to return the data we want to test against... diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index cab9de9204..7ebdfabbb0 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -4,15 +4,19 @@ using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing.CompositionExtensions; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Services; using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Models.ContentEditing; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Models.Mapping @@ -21,11 +25,37 @@ namespace Umbraco.Tests.Models.Mapping [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class ContentWebModelMappingTests : TestWithDatabaseBase { + private IContentTypeService _contentTypeService; + + protected override void Compose() { base.Compose(); Composition.RegisterUnique(f => Mock.Of()); + + Composition.Register(_ => Mock.Of()); + Composition.ComposeFileSystems(); + + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + + // all this is required so we can validate properties... + var editor = new TextboxPropertyEditor(Mock.Of()) { Alias = "test" }; + Composition.Register(_ => new DataEditorCollection(new[] { editor })); + Composition.Register(); + var dataType = Mock.Of(); + Mock.Get(dataType).Setup(x => x.Configuration).Returns(() => new object()); + var dataTypeService = Mock.Of(); + Mock.Get(dataTypeService) + .Setup(x => x.GetDataType(It.IsAny())) + .Returns(() => dataType); + + _contentTypeService = Mock.Of(); + var mediaTypeService = Mock.Of(); + var memberTypeService = Mock.Of(); + Composition.RegisterUnique(_ => _contentTypeService); + Composition.Register(_ => ServiceContext.CreatePartial(dataTypeService: dataTypeService, contentTypeBaseServiceProvider: new ContentTypeBaseServiceProvider(_contentTypeService, mediaTypeService, memberTypeService))); } [DataEditor("Test.Test", "Test", "~/Test.html")] @@ -52,6 +82,8 @@ namespace Umbraco.Tests.Models.Mapping public void To_Media_Item_Simple() { var contentType = MockedContentTypes.CreateImageMediaType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedMedia.CreateMediaImage(contentType, -1); FixUsers(content); @@ -69,6 +101,8 @@ namespace Umbraco.Tests.Models.Mapping public void To_Content_Item_Simple() { var contentType = MockedContentTypes.CreateSimpleContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateSimpleContent(contentType); FixUsers(content); @@ -86,6 +120,8 @@ namespace Umbraco.Tests.Models.Mapping public void To_Content_Item_Dto() { var contentType = MockedContentTypes.CreateSimpleContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateSimpleContent(contentType); FixUsers(content); @@ -116,12 +152,17 @@ namespace Umbraco.Tests.Models.Mapping public void To_Display_Model() { var contentType = MockedContentTypes.CreateSimpleContentType(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + Mock.Get(_contentTypeService).Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = MockedContent.CreateSimpleContent(contentType); + + FixUsers(content); // need ids for tabs var id = 1; - foreach (var g in content.PropertyGroups) + foreach (var g in contentType.CompositionPropertyGroups) g.Id = id++; var result = Mapper.Map(content); @@ -135,7 +176,7 @@ namespace Umbraco.Tests.Models.Mapping AssertDisplayProperty(invariantContent, p); } - Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count()); + Assert.AreEqual(contentType.CompositionPropertyGroups.Count(), invariantContent.Tabs.Count()); Assert.IsTrue(invariantContent.Tabs.First().IsActive); Assert.IsTrue(invariantContent.Tabs.Except(new[] { invariantContent.Tabs.First() }).All(x => x.IsActive == false)); } @@ -145,6 +186,9 @@ namespace Umbraco.Tests.Models.Mapping { var contentType = MockedContentTypes.CreateSimpleContentType(); contentType.PropertyGroups.Clear(); + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + Mock.Get(_contentTypeService).Setup(x => x.Get(It.IsAny())).Returns(contentType); + var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; var result = Mapper.Map(content); @@ -158,7 +202,7 @@ namespace Umbraco.Tests.Models.Mapping AssertDisplayProperty(invariantContent, p); } - Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count()); + Assert.AreEqual(contentType.CompositionPropertyGroups.Count(), invariantContent.Tabs.Count()); } [Test] @@ -176,6 +220,10 @@ namespace Umbraco.Tests.Models.Mapping p.Id = idSeed; idSeed++; } + Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); + Mock.Get(_contentTypeService).Setup(x => x.Get(It.IsAny())).Returns(contentType); + + var content = MockedContent.CreateSimpleContent(contentType); FixUsers(content); @@ -186,7 +234,7 @@ namespace Umbraco.Tests.Models.Mapping } //need ids for tabs var id = 1; - foreach (var g in content.PropertyGroups) + foreach (var g in contentType.CompositionPropertyGroups) { g.Id = id; id++; @@ -207,7 +255,7 @@ namespace Umbraco.Tests.Models.Mapping AssertDisplayProperty(invariantContent, p); } - Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count() - 1); + Assert.AreEqual(contentType.CompositionPropertyGroups.Count(), invariantContent.Tabs.Count() - 1); Assert.IsTrue(invariantContent.Tabs.Any(x => x.Label == Current.Services.TextService.Localize("general/properties"))); Assert.AreEqual(2, invariantContent.Tabs.Where(x => x.Label == Current.Services.TextService.Localize("general/properties")).SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count()); } diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs index c09f2e9460..5e92ad7ccf 100644 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -67,20 +67,7 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Id, member.Id); Assert.AreEqual(clone.VersionId, member.VersionId); Assert.AreEqual(clone.AdditionalData, member.AdditionalData); - Assert.AreNotSame(clone.ContentType, member.ContentType); Assert.AreEqual(clone.ContentType, member.ContentType); - Assert.AreEqual(clone.ContentType.PropertyGroups.Count, member.ContentType.PropertyGroups.Count); - for (var index = 0; index < member.ContentType.PropertyGroups.Count; index++) - { - Assert.AreNotSame(clone.ContentType.PropertyGroups[index], member.ContentType.PropertyGroups[index]); - Assert.AreEqual(clone.ContentType.PropertyGroups[index], member.ContentType.PropertyGroups[index]); - } - Assert.AreEqual(clone.ContentType.PropertyTypes.Count(), member.ContentType.PropertyTypes.Count()); - for (var index = 0; index < member.ContentType.PropertyTypes.Count(); index++) - { - Assert.AreNotSame(clone.ContentType.PropertyTypes.ElementAt(index), member.ContentType.PropertyTypes.ElementAt(index)); - Assert.AreEqual(clone.ContentType.PropertyTypes.ElementAt(index), member.ContentType.PropertyTypes.ElementAt(index)); - } Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); Assert.AreEqual(clone.CreateDate, member.CreateDate); Assert.AreEqual(clone.CreatorId, member.CreatorId); @@ -112,6 +99,9 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Properties[index], member.Properties[index]); } + // this can be the same, it is immutable + Assert.AreSame(clone.ContentType, member.ContentType); + //This double verifies by reflection var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index 103fd35074..5569ecfa60 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -135,7 +135,7 @@ namespace Umbraco.Tests.Models Assert.Throws(() => prop.PublishValues()); // change - propertyType.IsPublishing = true; + propertyType.SupportsPublishing = true; // can get value // and now published value is null @@ -227,6 +227,9 @@ namespace Umbraco.Tests.Models // now it will work contentType.Variations = ContentVariation.Culture; + // recreate content to re-capture content type variations + content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; + // invariant name works content.Name = "name"; Assert.AreEqual("name", content.GetCultureName(null)); @@ -290,6 +293,7 @@ namespace Umbraco.Tests.Models // change - now we vary by culture contentType.Variations |= ContentVariation.Culture; propertyType.Variations |= ContentVariation.Culture; + content.ChangeContentType(contentType); // can set value // and get values @@ -360,6 +364,7 @@ namespace Umbraco.Tests.Models [Test] public void ContentPublishValuesWithMixedPropertyTypeVariations() { + var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance(), Current.Factory.GetInstance().DataTypeService); const string langFr = "fr-FR"; // content type varies by Culture @@ -379,17 +384,18 @@ namespace Umbraco.Tests.Models content.SetCultureName("hello", langFr); - Assert.IsFalse(content.PublishCulture(langFr)); // fails because prop1 is mandatory - content.SetValue("prop1", "a", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); - Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true)); - //this will be null because we tried to publish values for a specific culture but this property is invariant - Assert.IsNull(content.GetValue("prop2", published: true)); + Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) + Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop1 is mandatory - Assert.IsFalse(content.PublishCulture()); // fails because prop2 is mandatory - content.SetValue("prop2", "b"); - Assert.IsTrue(content.PublishCulture()); - Assert.AreEqual("b", content.GetValue("prop2", published: true)); + content.SetValue("prop1", "a", langFr); + Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) + Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop2 is mandatory and invariant + content.SetValue("prop2", "x"); + Assert.IsTrue(content.PublishCulture(langFr)); // still ok... + Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// now it's ok + + Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true)); + Assert.AreEqual("x", content.GetValue("prop2", published: true)); } [Test] @@ -409,6 +415,8 @@ namespace Umbraco.Tests.Models contentType.Variations |= ContentVariation.Culture; propertyType.Variations |= ContentVariation.Culture; + content.ChangeContentType(contentType); + Assert.Throws(() => content.SetValue("prop", "a")); // invariant = no content.SetValue("prop", "a-fr", langFr); content.SetValue("prop", "a-uk", langUk); @@ -477,20 +485,21 @@ namespace Umbraco.Tests.Models [Test] public void ValidationTests() { - var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop", IsPublishing = true }; + var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop", SupportsPublishing = true }; var prop = new Property(propertyType); prop.SetValue("a"); Assert.AreEqual("a", prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); + var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance(), Current.Factory.GetInstance().DataTypeService); - Assert.IsTrue(prop.IsValid()); + Assert.IsTrue(propertyValidationService.IsPropertyValid(prop)); propertyType.Mandatory = true; - Assert.IsTrue(prop.IsValid()); + Assert.IsTrue(propertyValidationService.IsPropertyValid(prop)); prop.SetValue(null); - Assert.IsFalse(prop.IsValid()); + Assert.IsFalse(propertyValidationService.IsPropertyValid(prop)); // can publish, even though invalid prop.PublishValues(); diff --git a/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs b/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs index ba71caad95..51df7d1f2f 100644 --- a/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs +++ b/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs @@ -5,7 +5,6 @@ using System.Xml.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.Composers; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Packaging; @@ -16,6 +15,7 @@ using Umbraco.Core.Services; using Umbraco.Tests.Services; using Umbraco.Tests.Services.Importing; using Umbraco.Tests.Testing; +using Umbraco.Core.Composing.CompositionExtensions; namespace Umbraco.Tests.Packaging { diff --git a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs index 731ce73d3d..abf61370a8 100644 --- a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs +++ b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 2d45c24d0f..03aae74920 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -311,7 +311,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(4, contentType.PropertyTypes.Count()); - // remove all templates - since they are not saved, they would break the (wtf) mapping code + // remove all templates - since they are not saved, they would break the (!) mapping code contentType.AllowedTemplates = new ITemplate[0]; // there is NO mapping from display to contentType, but only from save diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index b9724b0770..b558ce6c87 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -192,7 +192,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var repository = CreateRepository(); // Act - var dataTypeDefinition = repository.Get(-42); + var dataTypeDefinition = repository.Get(Constants.DataTypes.DropDownSingle); // Assert Assert.That(dataTypeDefinition, Is.Not.Null); @@ -344,7 +344,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(definitionUpdated, Is.Not.Null); Assert.That(definitionUpdated.Name, Is.EqualTo("AgeDataType Updated")); - Assert.That(definitionUpdated.EditorAlias, Is.EqualTo(Constants.PropertyEditors.Aliases.NoEdit)); + Assert.That(definitionUpdated.EditorAlias, Is.EqualTo(Constants.PropertyEditors.Aliases.Label)); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index c2bfd77266..2c4f3f1908 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -130,10 +130,11 @@ namespace Umbraco.Tests.Persistence.Repositories var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); ServiceContext.FileService.SaveTemplate(hasPropertiesContentType.DefaultTemplate); // else, FK violation on contentType! + contentTypeRepository.Save(hasPropertiesContentType); + IContent content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType); // save = create the initial version - contentTypeRepository.Save(hasPropertiesContentType); repository.Save(content1); versions.Add(content1.VersionId); // the first version @@ -301,13 +302,15 @@ namespace Umbraco.Tests.Persistence.Repositories var emptyContentType = MockedContentTypes.CreateBasicContentType(); var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + contentTypeRepository.Save(emptyContentType); + contentTypeRepository.Save(hasPropertiesContentType); + ServiceContext.FileService.SaveTemplate(hasPropertiesContentType.DefaultTemplate); // else, FK violation on contentType! var content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType); var content2 = MockedContent.CreateBasicContent(emptyContentType); var content3 = MockedContent.CreateSimpleContent(hasPropertiesContentType); - contentTypeRepository.Save(emptyContentType); - contentTypeRepository.Save(hasPropertiesContentType); + repository.Save(content1); repository.Save(content2); repository.Save(content3); @@ -397,9 +400,10 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out var contentTypeRepository); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! + contentTypeRepository.Save(contentType); + IContent textpage = MockedContent.CreateSimpleContent(contentType); - contentTypeRepository.Save(contentType); repository.Save(textpage); scope.Complete(); @@ -423,9 +427,9 @@ namespace Umbraco.Tests.Persistence.Repositories var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); contentType.AllowedTemplates = Enumerable.Empty(); // because CreateSimpleContentType assigns one already contentType.SetDefaultTemplate(template); - var textpage = MockedContent.CreateSimpleContent(contentType); - contentTypeRepository.Save(contentType); + + var textpage = MockedContent.CreateSimpleContent(contentType); repository.Save(textpage); @@ -485,9 +489,10 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out var contentTypeRepository); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! + contentTypeRepository.Save(contentType); + var textpage = MockedContent.CreateSimpleContent(contentType); - contentTypeRepository.Save(contentType); repository.Save(textpage); diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index 5823537f7a..39f6e3e114 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -79,7 +79,39 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Perform_Get_By_Invariant_Code_On_LanguageRepository() + { + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + var es = new CultureInfo("es"); + var esSpecific = new CultureInfo("es-ES"); + + var language = (ILanguage)new Language(es.Name) + { + CultureName = es.DisplayName, + FallbackLanguageId = 1 + }; + repository.Save(language); + + language = repository.GetByIsoCode(es.Name); + var languageSpecific = repository.GetByIsoCode(esSpecific.Name); + + // Assert + Assert.That(language, Is.Not.Null); + Assert.That(language.HasIdentity, Is.True); + Assert.That(language.IsoCode, Is.EqualTo(es.Name)); + + Assert.That(languageSpecific, Is.Not.Null); + Assert.That(languageSpecific.HasIdentity, Is.True); + Assert.That(languageSpecific.Id, Is.EqualTo(language.Id)); + Assert.That(language.IsoCode, Is.EqualTo(language.IsoCode)); + } + } + [Test] public void Get_When_Id_Doesnt_Exist_Returns_Null() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index cbd591950a..1c1b5e60f4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -61,8 +61,9 @@ namespace Umbraco.Tests.Persistence.Repositories udb.EnableSqlCount = false; var mediaType = MockedContentTypes.CreateSimpleMediaType("umbTextpage1", "Textpage"); - var media = MockedMedia.CreateSimpleMedia(mediaType, "hello", -1); mediaTypeRepository.Save(mediaType); + + var media = MockedMedia.CreateSimpleMedia(mediaType, "hello", -1); repository.Save(media); udb.EnableSqlCount = true; @@ -271,7 +272,7 @@ namespace Umbraco.Tests.Persistence.Repositories var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); repository.Save(folder); } - + var types = new[] { 1031 }; var query = scope.SqlContext.Query().Where(x => types.Contains(x.ContentTypeId)); @@ -302,7 +303,7 @@ namespace Umbraco.Tests.Persistence.Repositories var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); repository.Save(folder); } - + var types = new[] { "Folder" }; var query = scope.SqlContext.Query().Where(x => types.Contains(x.ContentType.Alias)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index dea15cd4ad..c46a090685 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -164,21 +164,19 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType = MockedContentTypes.CreateSimpleMemberType(); memberTypeRepository.Save(memberType); - var member = MockedMember.CreateSimpleMember(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty"); repository.Save(member); - var sut = repository.Get(member.Id); - Assert.That(sut.ContentType.PropertyGroups.Count(), Is.EqualTo(2)); - Assert.That(sut.ContentType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); + Assert.That(memberType.CompositionPropertyGroups.Count(), Is.EqualTo(2)); + Assert.That(memberType.CompositionPropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); Assert.That(sut.Properties.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); - var grp = sut.PropertyGroups.FirstOrDefault(x => x.Name == Constants.Conventions.Member.StandardPropertiesGroupName); + var grp = memberType.CompositionPropertyGroups.FirstOrDefault(x => x.Name == Constants.Conventions.Member.StandardPropertiesGroupName); Assert.IsNotNull(grp); var aliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); - foreach (var p in sut.PropertyTypes.Where(x => aliases.Contains(x.Alias))) + foreach (var p in memberType.CompositionPropertyTypes.Where(x => aliases.Contains(x.Alias))) { Assert.AreEqual(grp.Id, p.PropertyGroupId.Value); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 8f750f9238..1d50fcac9e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act repository.Save(user); - + // Assert Assert.That(user.HasIdentity, Is.True); @@ -96,9 +96,9 @@ namespace Umbraco.Tests.Persistence.Repositories // Act repository.Save(user1); - + repository.Save(use2); - + // Assert Assert.That(user1.HasIdentity, Is.True); @@ -117,7 +117,7 @@ namespace Umbraco.Tests.Persistence.Repositories var user = MockedUser.CreateUser(); repository.Save(user); - + // Act var resolved = repository.Get((int)user.Id); @@ -132,9 +132,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_UserRepository() { var ct = MockedContentTypes.CreateBasicContentType("test"); - var content = MockedContent.CreateBasicContent(ct); var mt = MockedContentTypes.CreateSimpleMediaType("testmedia", "TestMedia"); - var media = MockedMedia.CreateSimpleMedia(mt, "asdf", -1); // Arrange var provider = TestObjects.GetScopeProvider(Logger); @@ -147,11 +145,12 @@ namespace Umbraco.Tests.Persistence.Repositories contentTypeRepo.Save(ct); mediaTypeRepo.Save(mt); - + + var content = MockedContent.CreateBasicContent(ct); + var media = MockedMedia.CreateSimpleMedia(mt, "asdf", -1); contentRepository.Save(content); mediaRepository.Save(media); - var user = CreateAndCommitUserWithGroup(userRepository, userGroupRepository); @@ -171,7 +170,7 @@ namespace Umbraco.Tests.Persistence.Repositories resolved.Username = "newName"; userRepository.Save(resolved); - + var updatedItem = (User) userRepository.Get(user.Id); // Assert @@ -204,13 +203,13 @@ namespace Umbraco.Tests.Persistence.Repositories // Act repository.Save(user); - + var id = user.Id; var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of(),TestObjects.GetGlobalSettings()); repository2.Delete(user); - + var resolved = repository2.Get((int) id); @@ -373,7 +372,7 @@ namespace Umbraco.Tests.Persistence.Repositories scope.Database.AsUmbracoDatabase().EnableSqlCount = false; } } - + } [Test] @@ -429,7 +428,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var user = MockedUser.CreateUser(); repository.Save(user); - + var group = MockedUserGroup.CreateUserGroup(); userGroupRepository.AddOrUpdateGroupWithUsers(@group, new[] { user.Id }); diff --git a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs index 2c875d6afc..c7118dac79 100644 --- a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs +++ b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs @@ -1,9 +1,11 @@ -using Moq; +using System.Linq; +using Moq; using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -21,7 +23,9 @@ namespace Umbraco.Tests.Persistence using (var scope = ScopeProvider.CreateScope()) { var schema = new DatabaseSchemaCreator(scope.Database, Logger); - result = schema.ValidateSchema(); + result = schema.ValidateSchema( + //TODO: When we remove the xml cache from tests we can remove this too + DatabaseSchemaCreator.OrderedTables.Concat(new []{typeof(ContentXmlDto), typeof(PreviewXmlDto)})); } // Assert diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index 38daa2c1a7..dcbf5919eb 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index 557de9eb11..1d87bb35e7 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index c55da764e2..8d2ab84d35 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs index 7ec23158f6..43c1a83d33 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs @@ -65,9 +65,9 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(expected, result); } - [TestCase("apples", new[] { "apples" })] - [TestCase("apples,oranges", new[] { "apples", "oranges" })] - [TestCase(" apples, oranges, pears ", new[] { "apples", "oranges", "pears" })] + [TestCase("[\"apples\"]", new[] { "apples" })] + [TestCase("[\"apples\",\"oranges\"]", new[] { "apples", "oranges" })] + [TestCase("[\"apples\",\"oranges\",\"pears\"]", new[] { "apples", "oranges", "pears" })] [TestCase("", new string[] { })] [TestCase(null, new string[] { })] public void CanConvertCheckboxListPropertyEditor(object value, IEnumerable expected) @@ -78,9 +78,9 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(expected, result); } - [TestCase("apples", new[] { "apples" })] - [TestCase("apples,oranges", new[] { "apples", "oranges" })] - [TestCase("apples , oranges, pears ", new[] { "apples", "oranges", "pears" })] + [TestCase("[\"apples\"]", new[] { "apples" })] + [TestCase("[\"apples\",\"oranges\"]", new[] { "apples", "oranges" })] + [TestCase("[\"apples\",\"oranges\",\"pears\"]", new[] { "apples", "oranges", "pears" })] [TestCase("", new string[] { })] [TestCase(null, new string[] { })] public void CanConvertDropdownListMultiplePropertyEditor(object value, IEnumerable expected) @@ -104,7 +104,7 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(expected, result); } - + [TestCase("1", 1)] [TestCase("1", 1)] [TestCase("0", 0)] diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index ea5fbcaa06..764f6ac4a4 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -3,7 +3,6 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index eb0f1a9368..0fce8ebfc3 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -5,7 +5,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 4f18320fe8..ad2b0220bb 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; +using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; @@ -124,6 +125,9 @@ namespace Umbraco.Tests.PublishedContent Mock.Get(contentTypeService).Setup(x => x.GetAll()).Returns(contentTypes); Mock.Get(contentTypeService).Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes); + var contentTypeServiceBaseFactory = Mock.Of(); + Mock.Get(contentTypeServiceBaseFactory).Setup(x => x.For(It.IsAny())).Returns(contentTypeService); + var dataTypeService = Mock.Of(); Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); @@ -177,7 +181,9 @@ namespace Umbraco.Tests.PublishedContent dataSource, globalSettings, new SiteDomainHelper(), - Mock.Of()); + Mock.Of(), + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index ac1c57d409..108bfb9f18 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -4,12 +4,12 @@ using System.Collections.ObjectModel; using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.Testing; using Umbraco.Web; +using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.PublishedContent { @@ -186,7 +186,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Populated_Requested_Language() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); var value = content.Value("welcomeText", "en-US"); Assert.AreEqual("Welcome", value); } @@ -194,7 +194,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Populated_Requested_Non_Default_Language() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); var value = content.Value("welcomeText", "de"); Assert.AreEqual("Willkommen", value); } @@ -202,7 +202,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); var value = content.Value("welcomeText", "fr"); Assert.IsNull(value); } @@ -210,7 +210,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); var value = content.Value("welcomeText", "es"); Assert.IsNull(value); } @@ -218,7 +218,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); var value = content.Value("welcomeText", "es", fallback: Fallback.ToLanguage); Assert.AreEqual("Welcome", value); } @@ -226,7 +226,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); Assert.AreEqual("Welcome", value); } @@ -234,7 +234,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); var value = content.Value("welcomeText", "no", fallback: Fallback.ToLanguage); Assert.IsNull(value); } @@ -242,7 +242,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText2"); Assert.IsNull(value); } @@ -250,7 +250,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); } @@ -258,7 +258,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested2() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2"); Assert.IsNull(value); @@ -267,7 +267,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively2() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); @@ -276,7 +276,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively3() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); Assert.IsNull(content.GetProperty("noprop")); var value = content.Value("noprop", fallback: Fallback.ToAncestors); // property has no value but we still get the value (ie, the converter would do something) @@ -287,7 +287,7 @@ namespace Umbraco.Tests.PublishedContent public void Can_Get_Content_With_Recursive_Priority() { Current.VariationContextAccessor.VariationContext = new VariationContext("nl"); - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); @@ -298,7 +298,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_With_Fallback_Language_Priority() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage); // No Dutch value is directly assigned. Check has fallen back to English value from language variant. @@ -308,14 +308,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Throws_For_Non_Supported_Fallback() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); Assert.Throws(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999))); } [Test] public void Can_Fallback_To_Default_Value() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); // no Dutch value is assigned, so getting null var value = content.Value("welcomeText", "nl"); @@ -333,7 +333,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Have_Custom_Default_Value() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); // HACK: the value, pretend the converter would return something var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 5f3a51f4f6..b2f1f311c3 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.Core; using Umbraco.Tests.Testing; +using Umbraco.Web.Composing; namespace Umbraco.Tests.PublishedContent { @@ -95,14 +96,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void First() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); Assert.AreEqual("Content 1", content.Name); } [Test] public void Distinct() { - var items = UmbracoContext.Current.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.ContentCache.GetAtRoot() .Distinct() .Distinct() .ToIndexedArray(); @@ -126,7 +127,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType1() { - var items = UmbracoContext.Current.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.ContentCache.GetAtRoot() .OfType() .Distinct() .ToIndexedArray(); @@ -137,7 +138,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType2() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.ContentCache.GetAtRoot() .OfType() .Distinct() .ToIndexedArray(); @@ -148,7 +149,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.ContentCache.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); Assert.AreEqual("Content 2", content.Name); @@ -158,7 +159,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Position() { - var items = UmbracoContext.Current.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.ContentCache.GetAtRoot() .Where(x => x.Value("prop1") == 1234) .ToIndexedArray(); @@ -173,7 +174,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Issue() { - var content = UmbracoContext.Current.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.ContentCache.GetAtRoot() .Distinct() .OfType(); @@ -181,12 +182,12 @@ namespace Umbraco.Tests.PublishedContent var first = where.First(); Assert.AreEqual(1234, first.Prop1); - var content2 = UmbracoContext.Current.ContentCache.GetAtRoot() + var content2 = Current.UmbracoContext.ContentCache.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); Assert.AreEqual(1234, content2.Prop1); - var content3 = UmbracoContext.Current.ContentCache.GetAtRoot() + var content3 = Current.UmbracoContext.ContentCache.GetAtRoot() .OfType() .First(); Assert.AreEqual(1234, content3.Prop1); @@ -195,7 +196,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void PublishedContentQueryTypedContentList() { - var query = new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache, UmbracoContext.Current.VariationContextAccessor); + var query = new PublishedContentQuery(Current.UmbracoContext.PublishedSnapshot, Current.UmbracoContext.VariationContextAccessor); var result = query.Content(new[] { 1, 2, 4 }).ToArray(); Assert.AreEqual(2, result.Length); Assert.AreEqual(1, result[0].Id); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs index e293653c37..7a9a882baa 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web.Routing; using Moq; +using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.Web.PublishedCache; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index a9022554c9..f801d02c5b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -23,6 +23,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Tests.LegacyXmlPublishedCache; +using Umbraco.Tests.Testing.Objects.Accessors; namespace Umbraco.Tests.PublishedContent { @@ -44,6 +45,8 @@ namespace Umbraco.Tests.PublishedContent Composition.WithCollectionBuilder() .Clear() .Append(); + + Composition.RegisterUnique(); } private IMediaType MakeNewMediaType(IUser user, string text, int parentId = -1) @@ -126,7 +129,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -156,7 +159,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); //ensure it is found var publishedMedia = cache.GetById(3113); @@ -203,7 +206,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -231,7 +234,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -259,7 +262,7 @@ namespace Umbraco.Tests.PublishedContent var searcher = indexer.GetSearcher(); var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -288,7 +291,7 @@ namespace Umbraco.Tests.PublishedContent var ctx = GetUmbracoContext("/test"); var searcher = indexer.GetSearcher(); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(3113); @@ -314,7 +317,7 @@ namespace Umbraco.Tests.PublishedContent var ctx = GetUmbracoContext("/test"); var searcher = indexer.GetSearcher(); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance()); + var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance(), Factory.GetInstance()); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(3113); diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index a838e06a9a..86017be820 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -39,6 +39,9 @@ namespace Umbraco.Tests.PublishedContent public IAppCache SnapshotCache => null; public IAppCache ElementsCache => null; + + public void Dispose() + { } } class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs index c40b2e5876..5efd8668d5 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index dfbe9b0cda..ac2e62b1ef 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 71e3836b3d..b7fd277ec7 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web.Mvc; using System.Web.Routing; +using System.Web.Security; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -17,12 +18,16 @@ using Umbraco.Web.WebApi; using Umbraco.Core.Strings; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Dictionary; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web.PublishedCache; using Umbraco.Web.Runtime; +using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Routing @@ -35,7 +40,7 @@ namespace Umbraco.Tests.Routing { base.SetUp(); - WebRuntimeComponent.CreateRoutes( + WebInitialComponent.CreateRoutes( new TestUmbracoContextAccessor(), TestObjects.GetGlobalSettings(), new SurfaceControllerTypeCollection(Enumerable.Empty()), @@ -98,7 +103,8 @@ namespace Umbraco.Tests.Routing frequest.PublishedContent = umbracoContext.ContentCache.GetById(1174); frequest.TemplateModel = template; - var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContext, Mock.Of())); + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); + var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of())); handler.GetHandlerForRoute(umbracoContext.HttpContext.Request.RequestContext, frequest); Assert.AreEqual("RenderMvc", routeData.Values["controller"].ToString()); @@ -133,10 +139,21 @@ namespace Umbraco.Tests.Routing frequest.PublishedContent = umbracoContext.ContentCache.GetById(1172); frequest.TemplateModel = template; + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var type = new AutoPublishedContentType(22, "CustomDocument", new PublishedPropertyType[] { }); ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; - var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContext, Mock.Of())); + var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of(), context => + { + var membershipHelper = new MembershipHelper( + umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); + return new CustomDocumentController(Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + new UmbracoHelper(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), membershipHelper)); + })); handler.GetHandlerForRoute(umbracoContext.HttpContext.Request.RequestContext, frequest); Assert.AreEqual("CustomDocument", routeData.Values["controller"].ToString()); @@ -172,8 +189,8 @@ namespace Umbraco.Tests.Routing /// public class CustomDocumentController : RenderMvcController { - public CustomDocumentController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger) - : base(globalSettings, umbracoContext, services, appCaches, logger, profilingLogger) + public CustomDocumentController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) + : base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger, umbracoHelper) { } diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index ff610cbc00..3e4c4f1ba9 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -45,7 +45,8 @@ namespace Umbraco.Tests.Routing runtime, logger, null, // FIXME: PublishedRouter complexities... - Mock.Of() + Mock.Of(), + Mock.Of() ); runtime.Level = RuntimeLevel.Run; diff --git a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs index a794e226f1..2e944211ca 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs @@ -1,6 +1,7 @@ using System; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index ee3bca42e9..5850153100 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -6,7 +6,7 @@ using Examine; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Components; +using Umbraco.Core.Compose; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 3790a49cfc..0f99b6b884 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -11,7 +11,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -52,7 +51,7 @@ namespace Umbraco.Tests.Runtimes // settings // reset the current version to 0.0.0, clear connection strings - ConfigurationManager.AppSettings["umbracoConfigurationStatus"] = ""; + ConfigurationManager.AppSettings[Constants.AppSettings.ConfigurationStatus] = ""; // FIXME: we need a better management of settings here (and, true config files?) // create the very basic and essential things we need @@ -61,7 +60,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = new AppCaches(); // FIXME: has HttpRuntime stuff? var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); - var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); var mainDom = new SimpleMainDom(); var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); @@ -82,7 +81,7 @@ namespace Umbraco.Tests.Runtimes var composerTypes = typeLoader.GetTypes() // all of them .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components - .Where(x => x != typeof(WebRuntimeComposer)); // exclude web runtime + .Where(x => x != typeof(WebInitialComposer)); // exclude web runtime var composers = new Composers(composition, composerTypes, profilingLogger); composers.Compose(); @@ -101,6 +100,7 @@ namespace Umbraco.Tests.Runtimes composition.WithCollectionBuilder().Append(); composition.RegisterUnique(); composition.RegisterUnique(f => ExamineManager.Instance); + composition.RegisterUnique(); // initialize some components only/individually composition.WithCollectionBuilder() @@ -179,8 +179,9 @@ namespace Umbraco.Tests.Runtimes // need an UmbracoCOntext to access the cache // FIXME: not exactly pretty, should not depend on HttpContext var httpContext = Mock.Of(); - var withUmbracoContext = UmbracoContext.EnsureContext(httpContext); - var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext; + var umbracoContextFactory = factory.GetInstance(); + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(httpContext); + var umbracoContext = umbracoContextReference.UmbracoContext; // assert that there is no published document var pcontent = umbracoContext.ContentCache.GetById(content.Id); @@ -218,7 +219,7 @@ namespace Umbraco.Tests.Runtimes // and the published document has a url Assert.AreEqual("/test/", pcontent.GetUrl()); - withUmbracoContext.Dispose(); + umbracoContextReference.Dispose(); mainDom.Stop(); components.Terminate(); @@ -241,7 +242,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); - var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); var runtimeState = Mock.Of(); Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); var mainDom = Mock.Of(); diff --git a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs index 27e5328f6f..b0d2788b58 100644 --- a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs +++ b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Runtimes new PluginViewEngine() }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(engines[0], Is.InstanceOf()); @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Runtimes new PluginViewEngine() }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); @@ -50,7 +50,7 @@ namespace Umbraco.Tests.Runtimes profiledEngine }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines[0], Is.SameAs(profiledEngine)); } @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Runtimes [Test] public void WrapViewEngines_CollectionIsNull_DoesNotThrow() { - Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null)); + Assert.DoesNotThrow(() => WebInitialComponent.WrapViewEngines(null)); } } } diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 4aa28b5975..001553a8ae 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -3,7 +3,6 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Components; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.IO; diff --git a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs index 0a23e4a1b9..b9d680780e 100644 --- a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs @@ -4,10 +4,11 @@ using System.Text; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.Composers; using Umbraco.Core.IO; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Core.Composing.CompositionExtensions; +using FileSystems = Umbraco.Core.IO.FileSystems; namespace Umbraco.Tests.Scoping { diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 31681067d6..1dcc928141 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -80,6 +81,7 @@ namespace Umbraco.Tests.Scoping var documentRepository = Mock.Of(); var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); + var contentTypeServiceBaseFactory = Current.Services.ContentTypeBaseServices; return new PublishedSnapshotService( options, @@ -97,7 +99,9 @@ namespace Umbraco.Tests.Scoping DefaultCultureAccessor, new DatabaseDataSource(), Factory.GetInstance(), new SiteDomainHelper(), - Factory.GetInstance()); + Factory.GetInstance(), + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) @@ -130,7 +134,7 @@ namespace Umbraco.Tests.Scoping var umbracoContext = GetUmbracoContextNu("http://example.com/", setSingleton: true); // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); // create document type, document diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index b85a79f326..c7e4ddcb19 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -11,9 +11,11 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Cache; using Moq; +using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Sync; +using Umbraco.Web; namespace Umbraco.Tests.Scoping { @@ -73,7 +75,7 @@ namespace Umbraco.Tests.Scoping // get user again - else we'd modify the one that's in the cache user = service.GetUserById(user.Id); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); @@ -154,7 +156,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(lang.Id, globalCached.Id); Assert.AreEqual("fr-FR", globalCached.IsoCode); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); @@ -246,7 +248,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual("item-key", globalCached.ItemKey); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 1d07ec074f..044965bc79 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Xml; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; @@ -15,6 +16,7 @@ using Umbraco.Core.Sync; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Web; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; @@ -91,7 +93,7 @@ namespace Umbraco.Tests.Scoping var item = new Content("name", -1, contentType); // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); // check xml in context = "before" @@ -204,7 +206,7 @@ namespace Umbraco.Tests.Scoping Current.Services.ContentTypeService.Save(contentType); // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); // check xml in context = "before" diff --git a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs index f0409d8928..e32df610b9 100644 --- a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs @@ -26,7 +26,7 @@ namespace Umbraco.Tests.Security public void ShouldAuthenticateRequest_When_Not_Configured() { //should force app ctx to show not-configured - ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", ""); + ConfigurationManager.AppSettings.Set(Constants.AppSettings.ConfigurationStatus, ""); var globalSettings = TestObjects.GetGlobalSettings(); var umbracoContext = new UmbracoContext( diff --git a/src/Umbraco.Tests/Services/AmbiguousEventTests.cs b/src/Umbraco.Tests/Services/AmbiguousEventTests.cs new file mode 100644 index 0000000000..e137da1188 --- /dev/null +++ b/src/Umbraco.Tests/Services/AmbiguousEventTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Reflection; +using System.Text; +using NUnit.Framework; +using Umbraco.Core.Events; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class AmbiguousEventTests + { + [Explicit] + [TestCase(typeof(ContentService))] + [TestCase(typeof(MediaService))] + public void ListAmbiguousEvents(Type serviceType) + { + var typedEventHandler = typeof(TypedEventHandler<,>); + + // get all events + var events = serviceType.GetEvents(BindingFlags.Static | BindingFlags.Public); + + string TypeName(Type type) + { + if (!type.IsGenericType) + return type.Name; + var sb = new StringBuilder(); + TypeNameSb(type, sb); + return sb.ToString(); + } + + void TypeNameSb(Type type, StringBuilder sb) + { + var name = type.Name; + var pos = name.IndexOf('`'); + name = pos > 0 ? name.Substring(0, pos) : name; + sb.Append(name); + if (!type.IsGenericType) + return; + sb.Append("<"); + var first = true; + foreach (var arg in type.GetGenericArguments()) + { + if (first) first = false; + else sb.Append(", "); + TypeNameSb(arg, sb); + } + sb.Append(">"); + } + + foreach (var e in events) + { + // only continue if this is a TypedEventHandler + if (!e.EventHandlerType.IsGenericType) continue; + var typeDef = e.EventHandlerType.GetGenericTypeDefinition(); + if (typedEventHandler != typeDef) continue; + + // get the event args type + var eventArgsType = e.EventHandlerType.GenericTypeArguments[1]; + + // try to find the event back, based upon sender type + args type + // exclude -ing (eg Saving) events, we don't deal with them in EventDefinitionBase (they always trigger) + var found = EventNameExtractor.FindEvents(serviceType, eventArgsType, EventNameExtractor.MatchIngNames); + + if (found.Length == 1) continue; + + if (found.Length == 0) + { + Console.WriteLine($"{typeof(ContentService).Name} {e.Name} {TypeName(eventArgsType)} NotFound"); + continue; + } + + Console.WriteLine($"{typeof(ContentService).Name} {e.Name} {TypeName(eventArgsType)} Ambiguous"); + Console.WriteLine("\t" + string.Join(", ", found)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ContentServiceEventTests.cs b/src/Umbraco.Tests/Services/ContentServiceEventTests.cs new file mode 100644 index 0000000000..0cb301fcad --- /dev/null +++ b/src/Umbraco.Tests/Services/ContentServiceEventTests.cs @@ -0,0 +1,385 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, + PublishedRepositoryEvents = true, + WithApplication = true, + Logger = UmbracoTestOptions.Logger.Console)] + public class ContentServiceEventTests : TestWithSomeContentBase + { + public override void SetUp() + { + base.SetUp(); + ContentRepositoryBase.ThrowOnWarning = true; + } + + public override void TearDown() + { + ContentRepositoryBase.ThrowOnWarning = false; + base.TearDown(); + } + + [Test] + public void Saving_Culture() + { + var languageService = ServiceContext.LocalizationService; + + languageService.Save(new Language("fr-FR")); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentType.Variations = ContentVariation.Culture; + foreach (var propertyType in contentType.PropertyTypes) + propertyType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + IContent document = new Content("content", -1, contentType); + document.SetCultureName("hello", "en-US"); + document.SetCultureName("bonjour", "fr-FR"); + contentService.Save(document); + + //re-get - dirty properties need resetting + document = contentService.GetById(document.Id); + + // properties: title, bodyText, keywords, description + document.SetValue("title", "title-en", "en-US"); + + void OnSaving(IContentService sender, ContentSavingEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.AreSame(document, saved); + + Assert.IsTrue(e.IsSavingCulture(saved, "en-US")); + Assert.IsFalse(e.IsSavingCulture(saved, "fr-FR")); + } + + void OnSaved(IContentService sender, ContentSavedEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.AreSame(document, saved); + + Assert.IsTrue(e.HasSavedCulture(saved, "en-US")); + Assert.IsFalse(e.HasSavedCulture(saved, "fr-FR")); + } + + ContentService.Saving += OnSaving; + ContentService.Saved += OnSaved; + try + { + contentService.Save(document); + } + finally + { + ContentService.Saving -= OnSaving; + ContentService.Saved -= OnSaved; + } + } + + [Test] + public void Saving_Set_Value() + { + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + IContent document = new Content("content", -1, contentType); + + void OnSaving(IContentService sender, ContentSavingEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); + + saved.SetValue("title", "title"); + } + + void OnSaved(IContentService sender, ContentSavedEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.AreSame("title", document.GetValue("title")); + + //we're only dealing with invariant here + var propValue = saved.Properties["title"].Values.First(x => x.Culture == null && x.Segment == null); + + Assert.AreEqual("title", propValue.EditedValue); + Assert.IsNull(propValue.PublishedValue); + } + + ContentService.Saving += OnSaving; + ContentService.Saved += OnSaved; + try + { + contentService.Save(document); + } + finally + { + ContentService.Saving -= OnSaving; + ContentService.Saved -= OnSaved; + } + + } + + [Test] + public void Publishing_Culture() + { + var languageService = ServiceContext.LocalizationService; + + languageService.Save(new Language("fr-FR")); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentType.Variations = ContentVariation.Culture; + foreach (var propertyType in contentType.PropertyTypes) + propertyType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + IContent document = new Content("content", -1, contentType); + document.SetCultureName("hello", "en-US"); + document.SetCultureName("bonjour", "fr-FR"); + contentService.Save(document); + + Assert.IsFalse(document.IsCulturePublished("fr-FR")); + Assert.IsFalse(document.IsCulturePublished("en-US")); + + //re-get - dirty properties need resetting + document = contentService.GetById(document.Id); + + void OnPublishing(IContentService sender, ContentPublishingEventArgs e) + { + var publishing = e.PublishedEntities.First(); + + Assert.AreSame(document, publishing); + + Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US")); + Assert.IsTrue(e.IsPublishingCulture(publishing, "fr-FR")); + } + + void OnPublished(IContentService sender, ContentPublishedEventArgs e) + { + var published = e.PublishedEntities.First(); + + Assert.AreSame(document, published); + + Assert.IsFalse(e.HasPublishedCulture(published, "en-US")); + Assert.IsTrue(e.HasPublishedCulture(published, "fr-FR")); + } + + ContentService.Publishing += OnPublishing; + ContentService.Published += OnPublished; + try + { + contentService.SaveAndPublish(document, "fr-FR"); + } + finally + { + ContentService.Publishing -= OnPublishing; + ContentService.Published -= OnPublished; + } + + document = contentService.GetById(document.Id); + + // ensure it works and does not throw + Assert.IsTrue(document.IsCulturePublished("fr-FR")); + Assert.IsFalse(document.IsCulturePublished("en-US")); + } + + [Test] + public void Publishing_Set_Value() + { + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + IContent document = new Content("content", -1, contentType); + + void OnSaving(IContentService sender, ContentSavingEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); + + saved.SetValue("title", "title"); + } + + void OnSaved(IContentService sender, ContentSavedEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.AreSame("title", document.GetValue("title")); + + //we're only dealing with invariant here + var propValue = saved.Properties["title"].Values.First(x => x.Culture == null && x.Segment == null); + + Assert.AreEqual("title", propValue.EditedValue); + Assert.AreEqual("title", propValue.PublishedValue); + } + + //We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be + //used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model + //are published, but Saving is raised BEFORE. + ContentService.Saving += OnSaving; + ContentService.Saved += OnSaved; + try + { + contentService.SaveAndPublish(document); + } + finally + { + ContentService.Saving -= OnSaving; + ContentService.Saved -= OnSaved; + } + } + + [Test] + public void Publishing_Set_Mandatory_Value() + { + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + var titleProperty = contentType.PropertyTypes.First(x => x.Alias == "title"); + titleProperty.Mandatory = true; // make this required! + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + IContent document = new Content("content", -1, contentType); + + var result = contentService.SaveAndPublish(document); + Assert.IsFalse(result.Success); + Assert.AreEqual("title", result.InvalidProperties.First().Alias); + + // when a service operation fails, the object is dirty and should not be re-used, + // re-create it + document = new Content("content", -1, contentType); + + void OnSaving(IContentService sender, ContentSavingEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); + + saved.SetValue("title", "title"); + } + + //We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be + //used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model + //are published, but Saving is raised BEFORE. + ContentService.Saving += OnSaving; + try + { + result = contentService.SaveAndPublish(document); + Assert.IsTrue(result.Success); //will succeed now because we were able to specify the required value in the Saving event + } + finally + { + ContentService.Saving -= OnSaving; + } + } + + [Test] + public void Unpublishing_Culture() + { + var languageService = ServiceContext.LocalizationService; + + languageService.Save(new Language("fr-FR")); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentType.Variations = ContentVariation.Culture; + foreach (var propertyType in contentType.PropertyTypes) + propertyType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = (ContentService)ServiceContext.ContentService; + + IContent document = new Content("content", -1, contentType); + document.SetCultureName("hello", "en-US"); + document.SetCultureName("bonjour", "fr-FR"); + contentService.SaveAndPublish(document); + + Assert.IsTrue(document.IsCulturePublished("fr-FR")); + Assert.IsTrue(document.IsCulturePublished("en-US")); + + //re-get - dirty properties need resetting + document = contentService.GetById(document.Id); + + void OnPublishing(IContentService sender, ContentPublishingEventArgs e) + { + var publishing = e.PublishedEntities.First(); + + Assert.AreSame(document, publishing); + + Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US")); + Assert.IsFalse(e.IsPublishingCulture(publishing, "fr-FR")); + + Assert.IsFalse(e.IsUnpublishingCulture(publishing, "en-US")); + Assert.IsTrue(e.IsUnpublishingCulture(publishing, "fr-FR")); + } + + void OnPublished(IContentService sender, ContentPublishedEventArgs e) + { + var published = e.PublishedEntities.First(); + + Assert.AreSame(document, published); + + Assert.IsFalse(e.HasPublishedCulture(published, "en-US")); + Assert.IsFalse(e.HasPublishedCulture(published, "fr-FR")); + + Assert.IsFalse(e.HasUnpublishedCulture(published, "en-US")); + Assert.IsTrue(e.HasUnpublishedCulture(published, "fr-FR")); + } + + document.UnpublishCulture("fr-FR"); + + ContentService.Publishing += OnPublishing; + ContentService.Published += OnPublished; + try + { + contentService.CommitDocumentChanges(document); + } + finally + { + ContentService.Publishing -= OnPublishing; + ContentService.Published -= OnPublished; + } + + document = contentService.GetById(document.Id); + + Assert.IsFalse(document.IsCulturePublished("fr-FR")); + Assert.IsTrue(document.IsCulturePublished("en-US")); + } + } +} diff --git a/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs b/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs index 4afd5e33eb..d2343d3dea 100644 --- a/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs +++ b/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs @@ -264,9 +264,7 @@ namespace Umbraco.Tests.Services vRoot.SetValue("vp", "changed.es", "es"); ServiceContext.ContentService.Save(vRoot); // now root has drafts in all cultures - iv1.PublishCulture("de"); - iv1.PublishCulture("ru"); - ServiceContext.ContentService.SavePublishing(iv1); // now iv1 de and ru are published + ServiceContext.ContentService.SaveAndPublish(iv1, new []{"de", "ru"}); // now iv1 de and ru are published iv1.SetValue("ip", "changed"); iv1.SetValue("vp", "changed.de", "de"); @@ -345,10 +343,8 @@ namespace Umbraco.Tests.Services iv11.SetValue("vp", "iv11.es", "es"); ServiceContext.ContentService.Save(iv11); - iv11.PublishCulture("de"); iv11.SetCultureName("iv11.ru", "ru"); - iv11.PublishCulture("ru"); - ServiceContext.ContentService.SavePublishing(iv11); + ServiceContext.ContentService.SaveAndPublish(iv11, new []{"de", "ru"}); Assert.AreEqual("iv11.de", iv11.GetValue("vp", "de", published: true)); Assert.AreEqual("iv11.ru", iv11.GetValue("vp", "ru", published: true)); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 37d34557bb..04cdc2aab7 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -6,7 +6,6 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -14,14 +13,13 @@ using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Events; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; -using System.Reflection; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; +using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.LegacyXmlPublishedCache; namespace Umbraco.Tests.Services { @@ -60,32 +58,6 @@ namespace Umbraco.Tests.Services Composition.RegisterUnique(factory => Mock.Of()); } - /// - /// Used to list out all ambiguous events that will require dispatching with a name - /// - [Test, Explicit] - public void List_Ambiguous_Events() - { - var events = ServiceContext.ContentService.GetType().GetEvents(BindingFlags.Static | BindingFlags.Public); - var typedEventHandler = typeof(TypedEventHandler<,>); - foreach(var e in events) - { - //only continue if this is a TypedEventHandler - if (!e.EventHandlerType.IsGenericType) continue; - var typeDef = e.EventHandlerType.GetGenericTypeDefinition(); - if (typedEventHandler != typeDef) continue; - - //get the event arg type - var eventArgType = e.EventHandlerType.GenericTypeArguments[1]; - - var found = EventNameExtractor.FindEvent(typeof(ContentService), eventArgType, EventNameExtractor.MatchIngNames); - if (!found.Success && found.Result.Error == EventNameExtractorError.Ambiguous) - { - Console.WriteLine($"Ambiguous event, source: {typeof(ContentService)}, args: {eventArgType}"); - } - } - } - [Test] public void Create_Blueprint() { @@ -96,7 +68,7 @@ namespace Umbraco.Tests.Services ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); contentTypeService.Save(contentType); - var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", -1); + var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", Constants.System.Root); blueprint.SetValue("title", "blueprint 1"); blueprint.SetValue("bodyText", "blueprint 2"); blueprint.SetValue("keywords", "blueprint 3"); @@ -122,7 +94,7 @@ namespace Umbraco.Tests.Services ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); contentTypeService.Save(contentType); - var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", -1); + var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", Constants.System.Root); blueprint.SetValue("title", "blueprint 1"); blueprint.SetValue("bodyText", "blueprint 2"); blueprint.SetValue("keywords", "blueprint 3"); @@ -139,29 +111,33 @@ namespace Umbraco.Tests.Services [Test] public void Create_Content_From_Blueprint() { - var contentService = ServiceContext.ContentService; - var contentTypeService = ServiceContext.ContentTypeService; + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + var contentService = ServiceContext.ContentService; + var contentTypeService = ServiceContext.ContentTypeService; - var contentType = MockedContentTypes.CreateTextPageContentType(); - ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); - contentTypeService.Save(contentType); + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentTypeService.Save(contentType); - var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", -1); - blueprint.SetValue("title", "blueprint 1"); - blueprint.SetValue("bodyText", "blueprint 2"); - blueprint.SetValue("keywords", "blueprint 3"); - blueprint.SetValue("description", "blueprint 4"); + var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", Constants.System.Root); + blueprint.SetValue("title", "blueprint 1"); + blueprint.SetValue("bodyText", "blueprint 2"); + blueprint.SetValue("keywords", "blueprint 3"); + blueprint.SetValue("description", "blueprint 4"); - contentService.SaveBlueprint(blueprint); + contentService.SaveBlueprint(blueprint); - var fromBlueprint = contentService.CreateContentFromBlueprint(blueprint, "hello world"); - contentService.Save(fromBlueprint); + var fromBlueprint = contentService.CreateContentFromBlueprint(blueprint, "hello world"); + contentService.Save(fromBlueprint); + + Assert.IsTrue(fromBlueprint.HasIdentity); + Assert.AreEqual("blueprint 1", fromBlueprint.Properties["title"].GetValue()); + Assert.AreEqual("blueprint 2", fromBlueprint.Properties["bodyText"].GetValue()); + Assert.AreEqual("blueprint 3", fromBlueprint.Properties["keywords"].GetValue()); + Assert.AreEqual("blueprint 4", fromBlueprint.Properties["description"].GetValue()); + } - Assert.IsTrue(fromBlueprint.HasIdentity); - Assert.AreEqual("blueprint 1", fromBlueprint.Properties["title"].GetValue()); - Assert.AreEqual("blueprint 2", fromBlueprint.Properties["bodyText"].GetValue()); - Assert.AreEqual("blueprint 3", fromBlueprint.Properties["keywords"].GetValue()); - Assert.AreEqual("blueprint 4", fromBlueprint.Properties["description"].GetValue()); } [Test] @@ -179,7 +155,7 @@ namespace Umbraco.Tests.Services for (int i = 0; i < 10; i++) { - var blueprint = MockedContent.CreateTextpageContent(i % 2 == 0 ? ct1 : ct2, "hello" + i, -1); + var blueprint = MockedContent.CreateTextpageContent(i % 2 == 0 ? ct1 : ct2, "hello" + i, Constants.System.Root); contentService.SaveBlueprint(blueprint); } @@ -290,7 +266,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var content = contentService.CreateAndSave("Test", -1, "umbTextpage", Constants.Security.SuperUserId); + var content = contentService.CreateAndSave("Test", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); content.ContentSchedule.Add(null, DateTime.Now.AddHours(2)); contentService.Save(content, Constants.Security.SuperUserId); @@ -318,7 +294,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var content = contentService.CreateAndSave("Test", -1, "umbTextpage", Constants.Security.SuperUserId); + var content = contentService.CreateAndSave("Test", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); for (var i = 0; i < 20; i++) { content.SetValue("bodyText", "hello world " + Guid.NewGuid()); @@ -343,7 +319,7 @@ namespace Umbraco.Tests.Services var results = new List(); for (var i = 0; i < 20; i++) { - results.Add(contentService.CreateAndSave("Test", -1, "umbTextpage", 0)); + results.Add(contentService.CreateAndSave("Test", Constants.System.Root, "umbTextpage", 0)); } var sortedGet = contentService.GetByIds(new[] {results[10].Id, results[5].Id, results[12].Id}).ToArray(); @@ -363,7 +339,7 @@ namespace Umbraco.Tests.Services // Act for (int i = 0; i < 20; i++) { - contentService.CreateAndSave("Test", -1, "umbTextpage", Constants.Security.SuperUserId); + contentService.CreateAndSave("Test", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); } // Assert @@ -382,7 +358,7 @@ namespace Umbraco.Tests.Services // Act for (int i = 0; i < 20; i++) { - contentService.CreateAndSave("Test", -1, "umbBlah", Constants.Security.SuperUserId); + contentService.CreateAndSave("Test", Constants.System.Root, "umbBlah", Constants.Security.SuperUserId); } // Assert @@ -397,7 +373,7 @@ namespace Umbraco.Tests.Services var contentTypeService = ServiceContext.ContentTypeService; var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type"); contentTypeService.Save(contentType); - var parent = contentService.CreateAndSave("Test", -1, "umbBlah", Constants.Security.SuperUserId); + var parent = contentService.CreateAndSave("Test", Constants.System.Root, "umbBlah", Constants.Security.SuperUserId); // Act for (int i = 0; i < 20; i++) @@ -417,7 +393,7 @@ namespace Umbraco.Tests.Services var contentTypeService = ServiceContext.ContentTypeService; var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type"); contentTypeService.Save(contentType); - var parent = contentService.CreateAndSave("Test", -1, "umbBlah", Constants.Security.SuperUserId); + var parent = contentService.CreateAndSave("Test", Constants.System.Root, "umbBlah", Constants.Security.SuperUserId); // Act IContent current = parent; @@ -451,7 +427,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var content = contentService.Create("Test", -1, "umbTextpage", Constants.Security.SuperUserId); + var content = contentService.Create("Test", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); // Assert Assert.That(content, Is.Not.Null); @@ -465,7 +441,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var content = contentService.Create("Test", -1, "umbTextpage", Constants.Security.SuperUserId); + var content = contentService.Create("Test", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); // Assert Assert.That(content, Is.Not.Null); @@ -479,12 +455,12 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var content = contentService.Create("Test", -1, "umbTextpage"); + var content = contentService.Create("Test", Constants.System.Root, "umbTextpage"); // Assert Assert.That(content, Is.Not.Null); Assert.That(content.HasIdentity, Is.False); - Assert.That(content.CreatorId, Is.EqualTo(0)); //Default to 0 (unknown) since we didn't explicitly set this in the Create call + Assert.That(content.CreatorId, Is.EqualTo(Constants.Security.SuperUserId)); //Default to -1 aka SuperUser (unknown) since we didn't explicitly set this in the Create call } [Test] @@ -498,7 +474,7 @@ namespace Umbraco.Tests.Services RawPasswordValue = "test" }; ServiceContext.UserService.Save(user); - var content = new Content("Test", -1, ServiceContext.ContentTypeService.Get("umbTextpage")); + var content = new Content("Test", Constants.System.Root, ServiceContext.ContentTypeService.Get("umbTextpage")); // Act ServiceContext.ContentService.Save(content, (int)user.Id); @@ -515,7 +491,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act & Assert - Assert.Throws(() => contentService.Create("Test", -1, "umbAliasDoesntExist")); + Assert.Throws(() => contentService.Create("Test", Constants.System.Root, "umbAliasDoesntExist")); } [Test] @@ -523,7 +499,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = new Content(string.Empty, -1, ServiceContext.ContentTypeService.Get("umbTextpage")); + var content = new Content(string.Empty, Constants.System.Root, ServiceContext.ContentTypeService.Get("umbTextpage")); // Act & Assert Assert.Throws(() => contentService.Save(content)); @@ -753,42 +729,35 @@ namespace Umbraco.Tests.Services contentType.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(contentType); - IContent content = new Content("content", -1, contentType); + IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-fr", langFr.IsoCode); content.SetCultureName("content-en", langUk.IsoCode); content.PublishCulture(langFr.IsoCode); content.PublishCulture(langUk.IsoCode); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); - Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); //not persisted yet, will be false Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - Assert.IsFalse(content.WasCulturePublished(langUk.IsoCode)); //not persisted yet, will be false - var published = ServiceContext.ContentService.SavePublishing(content); + var published = ServiceContext.ContentService.SaveAndPublish(content, new[]{ langFr.IsoCode , langUk.IsoCode }); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + //re-get content = ServiceContext.ContentService.GetById(content.Id); Assert.IsTrue(published.Success); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); - Assert.IsTrue(content.WasCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - Assert.IsTrue(content.WasCulturePublished(langUk.IsoCode)); var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); Assert.IsTrue(unpublished.Success); Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result); - Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); - //this is slightly confusing but this will be false because this method is used for checking the state of the current model, - //but the state on the model has changed with the above Unpublish call - Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); //re-get content = ServiceContext.ContentService.GetById(content.Id); Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); - //this is slightly confusing but this will be false because this method is used for checking the state of a current model, - //but we've re-fetched from the database - Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - Assert.IsTrue(content.WasCulturePublished(langUk.IsoCode)); + } @@ -812,13 +781,12 @@ namespace Umbraco.Tests.Services ServiceContext.ContentTypeService.Save(contentType); - IContent content = new Content("content", -1, contentType); + IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-en", langGB.IsoCode); content.SetCultureName("content-fr", langFr.IsoCode); - content.PublishCulture(langGB.IsoCode); - content.PublishCulture(langFr.IsoCode); - Assert.IsTrue(ServiceContext.ContentService.SavePublishing(content).Success); - + + Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content, new []{ langGB.IsoCode , langFr.IsoCode }).Success); + //re-get content = ServiceContext.ContentService.GetById(content.Id); Assert.AreEqual(PublishedState.Published, content.PublishedState); @@ -855,10 +823,9 @@ namespace Umbraco.Tests.Services contentType.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(contentType); - IContent content = new Content("content", -1, contentType); + IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-fr", langFr.IsoCode); - content.PublishCulture(langFr.IsoCode); - var published = ServiceContext.ContentService.SavePublishing(content); + var published = ServiceContext.ContentService.SaveAndPublish(content, langFr.IsoCode); //audit log will only show that french was published var lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); Assert.AreEqual($"Published languages: French (France)", lastLog.Comment); @@ -866,8 +833,7 @@ namespace Umbraco.Tests.Services //re-get content = ServiceContext.ContentService.GetById(content.Id); content.SetCultureName("content-en", langGB.IsoCode); - content.PublishCulture(langGB.IsoCode); - published = ServiceContext.ContentService.SavePublishing(content); + published = ServiceContext.ContentService.SaveAndPublish(content, langGB.IsoCode); //audit log will only show that english was published lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); Assert.AreEqual($"Published languages: English (United Kingdom)", lastLog.Comment); @@ -888,18 +854,15 @@ namespace Umbraco.Tests.Services contentType.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(contentType); - IContent content = new Content("content", -1, contentType); + IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-fr", langFr.IsoCode); content.SetCultureName("content-gb", langGB.IsoCode); - content.PublishCulture(langGB.IsoCode); - content.PublishCulture(langFr.IsoCode); - var published = ServiceContext.ContentService.SavePublishing(content); + var published = ServiceContext.ContentService.SaveAndPublish(content, new[] {langGB.IsoCode, langFr.IsoCode}); Assert.IsTrue(published.Success); //re-get content = ServiceContext.ContentService.GetById(content.Id); - content.UnpublishCulture(langFr.IsoCode); //unpublish non-mandatory lang - var unpublished = ServiceContext.ContentService.SavePublishing(content); + var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //audit log will only show that french was unpublished var lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); Assert.AreEqual($"Unpublished languages: French (France)", lastLog.Comment); @@ -907,8 +870,7 @@ namespace Umbraco.Tests.Services //re-get content = ServiceContext.ContentService.GetById(content.Id); content.SetCultureName("content-en", langGB.IsoCode); - content.UnpublishCulture(langGB.IsoCode); //unpublish mandatory lang - unpublished = ServiceContext.ContentService.SavePublishing(content); + unpublished = ServiceContext.ContentService.Unpublish(content, langGB.IsoCode); //audit log will only show that english was published var logs = ServiceContext.AuditService.GetLogs(content.Id).ToList(); Assert.AreEqual($"Unpublished languages: English (United Kingdom)", logs[logs.Count - 2].Comment); @@ -923,8 +885,7 @@ namespace Umbraco.Tests.Services var content = contentService.GetById(NodeDto.NodeIdSeed + 2); // Act - content.PublishCulture(); - var published = contentService.SavePublishing(content, Constants.Security.SuperUserId); + var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); // Assert Assert.That(published.Success, Is.True); @@ -951,7 +912,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var parent = contentService.Create("parent", -1, "umbTextpage"); + var parent = contentService.Create("parent", Constants.System.Root, "umbTextpage"); contentService.SaveAndPublish(parent); var content = contentService.Create("child", parent, "umbTextpage"); @@ -978,8 +939,7 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Home", content.Name); content.Name = "foo"; - content.PublishCulture(); - var published = contentService.SavePublishing(content, Constants.Security.SuperUserId); + var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); Assert.That(published.Success, Is.True); Assert.That(content.Published, Is.True); @@ -1026,11 +986,11 @@ namespace Umbraco.Tests.Services Assert.IsTrue(parentPublished.Success); Assert.IsTrue(parent.Published); - var contentCanPublishValues = content.PublishCulture(); - // content cannot publish values because they are invalid - Assert.IsFalse(contentCanPublishValues); - Assert.IsNotEmpty(content.ValidateProperties()); + var propertyValidationService = new PropertyValidationService(Factory.GetInstance(), ServiceContext.DataTypeService); + var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties); + Assert.IsFalse(isValid); + Assert.IsNotEmpty(invalidProperties); // and therefore cannot be published, // because it did not have a published version at all @@ -1043,6 +1003,8 @@ namespace Umbraco.Tests.Services [Test] public void Can_Publish_And_Unpublish_Cultures_In_Single_Operation() { + //TODO: This is using an internal API - we aren't exposing this publicly (at least for now) but we'll keep the test around + var langFr = new Language("fr"); var langDa = new Language("da"); ServiceContext.LocalizationService.Save(langFr); @@ -1057,7 +1019,7 @@ namespace Umbraco.Tests.Services content.SetCultureName("name-da", langDa.IsoCode); content.PublishCulture(langFr.IsoCode); - var result = ServiceContext.ContentService.SavePublishing(content); + var result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content); Assert.IsTrue(result.Success); content = ServiceContext.ContentService.GetById(content.Id); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); @@ -1066,7 +1028,7 @@ namespace Umbraco.Tests.Services content.UnpublishCulture(langFr.IsoCode); content.PublishCulture(langDa.IsoCode); - result = ServiceContext.ContentService.SavePublishing(content); + result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content); Assert.IsTrue(result.Success); Assert.AreEqual(PublishResultType.SuccessMixedCulture, result.Result); @@ -1145,12 +1107,10 @@ namespace Umbraco.Tests.Services contentService.Save(content); var parent = contentService.GetById(NodeDto.NodeIdSeed + 2); - parent.PublishCulture(); - var parentPublished = contentService.SavePublishing(parent, Constants.Security.SuperUserId);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' + var parentPublished = contentService.SaveAndPublish(parent, userId: Constants.Security.SuperUserId);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' // Act - content.PublishCulture(); - var published = contentService.SavePublishing(content, Constants.Security.SuperUserId); + var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); // Assert Assert.That(parentPublished.Success, Is.True); @@ -1188,12 +1148,10 @@ namespace Umbraco.Tests.Services contentService.Save(content, Constants.Security.SuperUserId); var parent = contentService.GetById(NodeDto.NodeIdSeed + 2); - parent.PublishCulture(); - var parentPublished = contentService.SavePublishing(parent, Constants.Security.SuperUserId);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' + var parentPublished = contentService.SaveAndPublish(parent, userId: Constants.Security.SuperUserId);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' // Act - content.PublishCulture(); - var published = contentService.SavePublishing(content, Constants.Security.SuperUserId); + var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); // Assert Assert.That(parentPublished.Success, Is.True); @@ -1245,8 +1203,7 @@ namespace Umbraco.Tests.Services var content = contentService.GetById(NodeDto.NodeIdSeed + 5); // Act - content.PublishCulture(); - var published = contentService.SavePublishing(content, Constants.Security.SuperUserId); + var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); // Assert Assert.That(published.Success, Is.False); @@ -1263,8 +1220,7 @@ namespace Umbraco.Tests.Services content.SetValue("author", "Barack Obama"); // Act - content.PublishCulture(); - var published = contentService.SavePublishing(content, Constants.Security.SuperUserId); + var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); // Assert Assert.That(content.HasIdentity, Is.True); @@ -1284,19 +1240,17 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.Create("Home US", -1, "umbTextpage", Constants.Security.SuperUserId); + var content = contentService.Create("Home US", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); content.SetValue("author", "Barack Obama"); // Act - content.PublishCulture(); - var published = contentService.SavePublishing(content, Constants.Security.SuperUserId); + var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); var childContent = contentService.Create("Child", content.Id, "umbTextpage", Constants.Security.SuperUserId); // Reset all identity properties childContent.Id = 0; childContent.Path = null; ((Content)childContent).ResetIdentity(); - childContent.PublishCulture(); - var childPublished = contentService.SavePublishing(childContent, Constants.Security.SuperUserId); + var childPublished = contentService.SaveAndPublish(childContent, userId: Constants.Security.SuperUserId); // Assert Assert.That(content.HasIdentity, Is.True); @@ -1625,7 +1579,7 @@ namespace Umbraco.Tests.Services var contentType = MockedContentTypes.CreateAllTypesContentType("test", "test"); ServiceContext.ContentTypeService.Save(contentType, Constants.Security.SuperUserId); - + object obj = new { @@ -1635,14 +1589,12 @@ namespace Umbraco.Tests.Services content1.PropertyValues(obj); content1.ResetDirtyProperties(false); ServiceContext.ContentService.Save(content1, Constants.Security.SuperUserId); - content1.PublishCulture(); - Assert.IsTrue(ServiceContext.ContentService.SavePublishing(content1, 0).Success); + Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content1, userId: 0).Success); var content2 = MockedContent.CreateBasicContent(contentType); content2.PropertyValues(obj); content2.ResetDirtyProperties(false); ServiceContext.ContentService.Save(content2, Constants.Security.SuperUserId); - content2.PublishCulture(); - Assert.IsTrue(ServiceContext.ContentService.SavePublishing(content2, 0).Success); + Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content2, userId: 0).Success); var editorGroup = ServiceContext.UserService.GetUserGroupByAlias("editor"); editorGroup.StartContentId = content1.Id; @@ -1783,7 +1735,7 @@ namespace Umbraco.Tests.Services ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! ServiceContext.ContentTypeService.Save(contentType); - var content = MockedContent.CreateSimpleContent(contentType, "Simple Tags Page", -1); + var content = MockedContent.CreateSimpleContent(contentType, "Simple Tags Page", Constants.System.Root); content.AssignTags(propAlias, new[] {"hello", "world"}); contentService.Save(content); @@ -1959,7 +1911,7 @@ namespace Umbraco.Tests.Services ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! ServiceContext.ContentTypeService.Save(contentType); - var page = new Content("Page", -1, contentType) + var page = new Content("Page", Constants.System.Root, contentType) { Level = 1, SortOrder = 1, @@ -2186,7 +2138,7 @@ namespace Umbraco.Tests.Services var contentType = MockedContentTypes.CreateAllTypesContentType("allDataTypes", "All DataTypes"); contentTypeService.Save(contentType); var contentService = ServiceContext.ContentService; - var content = MockedContent.CreateAllTypesContent(contentType, "Random Content", -1); + var content = MockedContent.CreateAllTypesContent(contentType, "Random Content", Constants.System.Root); contentService.Save(content); var id = content.Id; @@ -2239,7 +2191,7 @@ namespace Umbraco.Tests.Services public void Ensure_Content_Xml_Created() { var contentService = ServiceContext.ContentService; - var content = contentService.Create("Home US", -1, "umbTextpage", Constants.Security.SuperUserId); + var content = contentService.Create("Home US", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); content.SetValue("author", "Barack Obama"); contentService.Save(content); @@ -2261,7 +2213,7 @@ namespace Umbraco.Tests.Services public void Ensure_Preview_Xml_Created() { var contentService = ServiceContext.ContentService; - var content = contentService.Create("Home US", -1, "umbTextpage", Constants.Security.SuperUserId); + var content = contentService.Create("Home US", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId); content.SetValue("author", "Barack Obama"); contentService.Save(content); @@ -2289,10 +2241,10 @@ namespace Umbraco.Tests.Services } long total; - var entities = service.GetPagedChildren(-1, 0, 6, out total).ToArray(); + var entities = service.GetPagedChildren(Constants.System.Root, 0, 6, out total).ToArray(); Assert.That(entities.Length, Is.EqualTo(6)); Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedChildren(-1, 1, 6, out total).ToArray(); + entities = service.GetPagedChildren(Constants.System.Root, 1, 6, out total).ToArray(); Assert.That(entities.Length, Is.EqualTo(4)); Assert.That(total, Is.EqualTo(10)); } @@ -2324,10 +2276,10 @@ namespace Umbraco.Tests.Services long total; // children in root including the folder - not the descendants in the folder - var entities = service.GetPagedChildren(-1, 0, 6, out total).ToArray(); + var entities = service.GetPagedChildren(Constants.System.Root, 0, 6, out total).ToArray(); Assert.That(entities.Length, Is.EqualTo(6)); Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedChildren(-1, 1, 6, out total).ToArray(); + entities = service.GetPagedChildren(Constants.System.Root, 1, 6, out total).ToArray(); Assert.That(entities.Length, Is.EqualTo(4)); Assert.That(total, Is.EqualTo(10)); @@ -2343,7 +2295,7 @@ namespace Umbraco.Tests.Services [Test] public void PublishingTest() { - var contentType = new ContentType(-1) + var contentType = new ContentType(Constants.System.Root) { Alias = "foo", Name = "Foo" @@ -2361,7 +2313,7 @@ namespace Umbraco.Tests.Services ServiceContext.ContentTypeService.Save(contentType); var contentService = ServiceContext.ContentService; - var content = contentService.Create("foo", -1, "foo"); + var content = contentService.Create("foo", Constants.System.Root, "foo"); contentService.Save(content); Assert.IsFalse(content.Published); @@ -2489,7 +2441,7 @@ namespace Umbraco.Tests.Services contentTypeService.Save(contentType); var contentService = ServiceContext.ContentService; - var content = new Content(null, -1, contentType); + var content = new Content(null, Constants.System.Root, contentType); content.SetCultureName("name-us", langUk.IsoCode); content.SetCultureName("name-fr", langFr.IsoCode); @@ -2524,7 +2476,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; - var content = new Content(null, -1, contentType); + var content = new Content(null, Constants.System.Root, contentType); content.SetCultureName("root", langUk.IsoCode); contentService.Save(content); @@ -2566,13 +2518,13 @@ namespace Umbraco.Tests.Services var o = new[] { 2, 1, 3, 0, 4 }; // randomly different for (var i = 0; i < 5; i++) { - var contentA = new Content(null, -1, contentType); + var contentA = new Content(null, Constants.System.Root, contentType); contentA.SetCultureName("contentA" + i + "uk", langUk.IsoCode); contentA.SetCultureName("contentA" + o[i] + "fr", langFr.IsoCode); contentA.SetCultureName("contentX" + i + "da", langDa.IsoCode); contentService.Save(contentA); - var contentB = new Content(null, -1, contentType); + var contentB = new Content(null, Constants.System.Root, contentType); contentB.SetCultureName("contentB" + i + "uk", langUk.IsoCode); contentB.SetCultureName("contentB" + o[i] + "fr", langFr.IsoCode); contentB.SetCultureName("contentX" + i + "da", langDa.IsoCode); @@ -2580,7 +2532,7 @@ namespace Umbraco.Tests.Services } // get all - var list = contentService.GetPagedChildren(-1, 0, 100, out var total).ToList(); + var list = contentService.GetPagedChildren(Constants.System.Root, 0, 100, out var total).ToList(); Console.WriteLine("ALL"); WriteList(list); @@ -2590,7 +2542,7 @@ namespace Umbraco.Tests.Services Assert.AreEqual(11, list.Count); // filter - list = contentService.GetPagedChildren(-1, 0, 100, out total, + list = contentService.GetPagedChildren(Constants.System.Root, 0, 100, out total, SqlContext.Query().Where(x => x.Name.Contains("contentX")), Ordering.By("name", culture: langFr.IsoCode)).ToList(); @@ -2598,7 +2550,7 @@ namespace Umbraco.Tests.Services Assert.AreEqual(0, list.Count); // filter - list = contentService.GetPagedChildren(-1, 0, 100, out total, + list = contentService.GetPagedChildren(Constants.System.Root, 0, 100, out total, SqlContext.Query().Where(x => x.Name.Contains("contentX")), Ordering.By("name", culture: langDa.IsoCode)).ToList(); @@ -2609,7 +2561,7 @@ namespace Umbraco.Tests.Services Assert.AreEqual(10, list.Count); // filter - list = contentService.GetPagedChildren(-1, 0, 100, out total, + list = contentService.GetPagedChildren(Constants.System.Root, 0, 100, out total, SqlContext.Query().Where(x => x.Name.Contains("contentA")), Ordering.By("name", culture: langFr.IsoCode)).ToList(); @@ -2622,7 +2574,7 @@ namespace Umbraco.Tests.Services for (var i = 0; i < 5; i++) Assert.AreEqual("contentA" + i + "fr", list[i].GetCultureName(langFr.IsoCode)); - list = contentService.GetPagedChildren(-1, 0, 100, out total, + list = contentService.GetPagedChildren(Constants.System.Root, 0, 100, out total, SqlContext.Query().Where(x => x.Name.Contains("contentA")), Ordering.By("name", direction: Direction.Descending, culture: langFr.IsoCode)).ToList(); @@ -2667,7 +2619,7 @@ namespace Umbraco.Tests.Services contentTypeService.Save(contentType); var contentService = ServiceContext.ContentService; - var content = contentService.Create("Home US", -1, "umbTextpage"); + var content = contentService.Create("Home US", Constants.System.Root, "umbTextpage"); // creating content with a name but no culture - will set the invariant name // but, because that content is variant, as soon as we save, we'll need to @@ -2717,9 +2669,7 @@ namespace Umbraco.Tests.Services // act - content.PublishCulture(langFr.IsoCode); - content.PublishCulture(langUk.IsoCode); - contentService.SavePublishing(content); + contentService.SaveAndPublish(content, new[]{ langFr.IsoCode, langUk.IsoCode }); // both FR and UK have been published, // and content has been published, @@ -2823,8 +2773,7 @@ namespace Umbraco.Tests.Services // act // cannot just 'save' since we are changing what's published! - content.UnpublishCulture(langFr.IsoCode); - contentService.SavePublishing(content); + contentService.Unpublish(content, langFr.IsoCode); // content has been published, // the french culture is gone @@ -2914,8 +2863,10 @@ namespace Umbraco.Tests.Services // act // that HAS to be SavePublishing, because SaveAndPublish would just republish everything! - - contentService.SavePublishing(content); + //TODO: This is using an internal API - the test can't pass without this but we want to keep the test here + // will need stephane to have a look at this test at some stage since there is a lot of logic here that we + // want to keep on testing but don't need the public API to do these more complicated things. + ((ContentService)contentService).CommitDocumentChanges(content); // content has been re-published, // everything is back to what it was before being unpublished @@ -2955,8 +2906,7 @@ namespace Umbraco.Tests.Services // act - content.PublishCulture(langUk.IsoCode); - contentService.SavePublishing(content); + contentService.SaveAndPublish(content, langUk.IsoCode); content2 = contentService.GetById(content.Id); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 1319d00c48..341371ca02 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Tests.Scoping; @@ -554,7 +555,6 @@ namespace Umbraco.Tests.Services IContent contentItem = MockedContent.CreateTextpageContent(contentType1, "Testing", -1); ServiceContext.ContentService.SaveAndPublish(contentItem); var initProps = contentItem.Properties.Count; - var initPropTypes = contentItem.PropertyTypes.Count(); //remove a property contentType1.RemovePropertyType(contentType1.PropertyTypes.First().Alias); @@ -563,7 +563,6 @@ namespace Umbraco.Tests.Services //re-load it from the db contentItem = ServiceContext.ContentService.GetById(contentItem.Id); - Assert.AreEqual(initPropTypes - 1, contentItem.PropertyTypes.Count()); Assert.AreEqual(initProps - 1, contentItem.Properties.Count); } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 3d9a36ca80..c70b96a175 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -53,6 +54,7 @@ namespace Umbraco.Tests.Services var documentRepository = Factory.GetInstance(); var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); + var contentTypeServiceBaseFactory = Current.Services.ContentTypeBaseServices; return new PublishedSnapshotService( options, @@ -70,7 +72,9 @@ namespace Umbraco.Tests.Services DefaultCultureAccessor, new DatabaseDataSource(), Factory.GetInstance(), new SiteDomainHelper(), - Factory.GetInstance()); + Factory.GetInstance(), + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); } public class LocalServerMessenger : ServerMessengerBase @@ -175,8 +179,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("v1en", document.GetValue("value1")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -194,8 +196,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("v1en", document.GetValue("value1")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -212,8 +212,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -288,8 +286,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("v1", document.GetValue("value1")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -305,8 +301,6 @@ namespace Umbraco.Tests.Services Assert.IsNull(document.GetValue("value1", "fr")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -324,8 +318,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("v1", document.GetValue("value1")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -401,8 +393,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("v1en", document.GetValue("value1")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -419,8 +409,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); Assert.AreEqual("v2", document.GetValue("value2")); - Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); @@ -439,8 +427,6 @@ namespace Umbraco.Tests.Services Assert.IsNull(document.GetValue("value2", "fr")); Assert.IsNull(document.GetValue("value2")); - Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value2").VariesByCulture()); - Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':"); diff --git a/src/Umbraco.Tests/Services/EntityXmlSerializerTests.cs b/src/Umbraco.Tests/Services/EntityXmlSerializerTests.cs index 28c69344b7..972595f2f1 100644 --- a/src/Umbraco.Tests/Services/EntityXmlSerializerTests.cs +++ b/src/Umbraco.Tests/Services/EntityXmlSerializerTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Xml.Linq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; diff --git a/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage-Random.xml b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage-Random.xml index 517db639a0..2ec534334f 100644 --- a/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage-Random.xml +++ b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage-Random.xml @@ -71,7 +71,7 @@ Content content - Umbraco.TinyMCEv3 + Umbraco.TinyMCE ca90c950-0aff-4e72-b976-a30b1ac57dad Content False @@ -172,4 +172,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage.xml b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage.xml index f6b4e2c160..39a7f102a5 100644 --- a/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage.xml +++ b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage.xml @@ -324,7 +324,7 @@ AboutText aboutText - Umbraco.TinyMCEv3 + Umbraco.TinyMCE ca90c950-0aff-4e72-b976-a30b1ac57dad About False @@ -472,7 +472,7 @@ Content bodyText - Umbraco.TinyMCEv3 + Umbraco.TinyMCE ca90c950-0aff-4e72-b976-a30b1ac57dad Content False @@ -593,7 +593,7 @@ Content bodyText - Umbraco.TinyMCEv3 + Umbraco.TinyMCE ca90c950-0aff-4e72-b976-a30b1ac57dad Content False @@ -721,8 +721,8 @@ + @@ -777,7 +777,7 @@ @{ Layout = "umbLayout.cshtml"; - // If the editor has not explicitly provided the "Page title" property page + // If the editor has not explicitly provided the "Page title" property page // then just show the name of the page otherwise show the provided title var pageTitle = string.IsNullOrWhiteSpace(CurrentPage.Title) ? CurrentPage.Name @@ -787,8 +787,8 @@ // AncestorsOrSelf is all of the ancestors this page has in the tree // (1) means: go up to level 1 and stop looking for more ancestors when you get there // First() gets the first ancestor found (the home page, on level 1) - var homePage = CurrentPage.AncestorsOrSelf(1).First(); - + var homePage = CurrentPage.AncestorsOrSelf(1).First(); + // Find all pages with document type alias umbNewsOverview // We do that using the plural, umbNewsOverviews (note the extra "s" in the end) // Then take the first one, as we know there will only be on news overview page @@ -815,18 +815,18 @@ @foreach (var item in newsItems) { - // If the editor has not explicitly provided the "Page title" property page + // If the editor has not explicitly provided the "Page title" property page // then just show the name of the page otherwise show the provided title - var title = string.IsNullOrWhiteSpace(item.Title) - ? item.Name + var title = string.IsNullOrWhiteSpace(item.Title) + ? item.Name : item.Title; // If the editor has not explicitly set the publishDate property then show the create date - var dateTime = item.PublishDate == default(DateTime) - ? item.CreateDate + var dateTime = item.PublishDate == default(DateTime) + ? item.CreateDate : item.PublishDate; - +

@title

@@ -869,11 +869,11 @@ @{ Layout = "umbLayout.cshtml"; - // If the editor has not explicitly provided the "Page title" property page + // If the editor has not explicitly provided the "Page title" property page // then just show the name of the page otherwise show the provided title var pageTitle = string.IsNullOrWhiteSpace(CurrentPage.Title) ? CurrentPage.Name - : CurrentPage.Title; + : CurrentPage.Title; }
@@ -892,7 +892,7 @@ } @CurrentPage.BodyText -
+ @@ -921,4 +921,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml b/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml index baadf0340f..8c353551dd 100644 --- a/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml +++ b/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml @@ -361,11 +361,6 @@ /Views/Partials/Grid blogpost.cshtml - - Bootstrap2.cshtml - /Views/Partials/Grid - Bootstrap2.cshtml - Bootstrap3.cshtml /Views/Partials/Grid @@ -2483,11 +2478,11 @@ @CurrentPage.siteTitle - + - + @@ -2522,13 +2517,13 @@ - + @RenderBody()
- +
Products
    @@ -2598,7 +2593,7 @@
- + diff --git a/src/Umbraco.Tests/Services/Importing/StandardMvc-Package.xml b/src/Umbraco.Tests/Services/Importing/StandardMvc-Package.xml index 06906e1db2..ee6f7cea4a 100644 --- a/src/Umbraco.Tests/Services/Importing/StandardMvc-Package.xml +++ b/src/Umbraco.Tests/Services/Importing/StandardMvc-Package.xml @@ -1767,7 +1767,7 @@ h1, h2, h3, h4, h5, h6, p, blockquote { h1, h2, h3, h4, h5, h6 { - font-family:'Open Sans', sans-serif; + font-family:'Lato', sans-serif; } h2, h3 diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index b9e1fee0db..17711fbd31 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 0c9c543b0d..cb55a891a7 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Web.Security.Providers; @@ -520,22 +521,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Test Real Name", member.Name); } - [Test] - public void Get_Member_Name_In_Created_Event() - { - IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); - ServiceContext.MemberTypeService.Save(memberType); - - TypedEventHandler> callback = (sender, args) => - { - Assert.AreEqual("Test Real Name", args.Entity.Name); - }; - - MemberService.Created += callback; - var member = ServiceContext.MemberService.CreateMember("testUsername", "test@test.com", "Test Real Name", memberType); - MemberService.Created -= callback; - } - [Test] public void Get_By_Username() { @@ -894,7 +879,7 @@ namespace Umbraco.Tests.Services public void Get_By_Property_Int_Value_Less_Than() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); - memberType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.Date, ValueStorageType.Date, "number") + memberType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.DateTime, ValueStorageType.Date, "number") { Name = "Number", //NOTE: This is what really determines the db type - the above definition doesn't really do anything diff --git a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs index 459daa3da6..24709469e9 100644 --- a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; @@ -83,7 +84,6 @@ namespace Umbraco.Tests.Services IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); ServiceContext.MemberService.Save(member); var initProps = member.Properties.Count; - var initPropTypes = member.PropertyTypes.Count(); //remove a property (NOT ONE OF THE DEFAULTS) var standardProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); @@ -93,7 +93,6 @@ namespace Umbraco.Tests.Services //re-load it from the db member = ServiceContext.MemberService.GetById(member.Id); - Assert.AreEqual(initPropTypes - 1, member.PropertyTypes.Count()); Assert.AreEqual(initProps - 1, member.Properties.Count); } diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 7d73d95e74..449e933c24 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.TestHelpers.Stubs; diff --git a/src/Umbraco.Tests/Services/SectionServiceTests.cs b/src/Umbraco.Tests/Services/SectionServiceTests.cs index d6bf26184b..cf0471a947 100644 --- a/src/Umbraco.Tests/Services/SectionServiceTests.cs +++ b/src/Umbraco.Tests/Services/SectionServiceTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using System.Linq; using System.Threading; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Testing; diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index f0db3991b8..4c365d733f 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; @@ -202,6 +203,20 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(expected, result); } + [TestCase("hello", "aGVsbG8")] + [TestCase("tad", "dGFk")] + [TestCase("AmqGr+Fd!~ééé", "QW1xR3IrRmQhfsOpw6nDqQ")] + public void UrlTokenEncoding(string value, string expected) + { + var bytes = Encoding.UTF8.GetBytes(value); + Console.WriteLine("base64: " + Convert.ToBase64String(bytes)); + var encoded = StringExtensions.UrlTokenEncode(bytes); + Assert.AreEqual(expected, encoded); + + var backBytes = StringExtensions.UrlTokenDecode(encoded); + var backString = Encoding.UTF8.GetString(backBytes); + Assert.AreEqual(value, backString); + } // FORMAT STRINGS diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 35fcc853d4..ca32e71e5b 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -3,12 +3,12 @@ using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Persistence; using Umbraco.Tests.Components; @@ -39,7 +39,7 @@ namespace Umbraco.Tests.TestHelpers var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeLoader = new TypeLoader(NoAppCache.Instance, - LocalTempStorage.Default, + IOHelper.MapPath("~/App_Data/TEMP"), logger, false); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivator.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivator.cs index 73762d4e91..6b67377202 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivator.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivator.cs @@ -7,16 +7,16 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { public class TestControllerActivator : TestControllerActivatorBase { - private readonly Func _factory; + private readonly Func _factory; - public TestControllerActivator(Func factory) + public TestControllerActivator(Func factory) { _factory = factory; } - protected override ApiController CreateController(Type controllerType, HttpRequestMessage msg, UmbracoHelper helper) + protected override ApiController CreateController(Type controllerType, HttpRequestMessage msg, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { - return _factory(msg, helper); + return _factory(msg, umbracoContextAccessor, helper); } } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 6414ea469d..c6bbebf550 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -9,7 +9,6 @@ using System.Web.Http.Dispatcher; using System.Web.Security; using Moq; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; using Umbraco.Core.Models.Membership; @@ -135,34 +134,33 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var umbracoContextAccessor = Umbraco.Web.Composing.Current.UmbracoContextAccessor; - var umbCtx = UmbracoContext.EnsureContext( - umbracoContextAccessor, - httpContext, + var umbCtx = new UmbracoContext(httpContext, publishedSnapshotService.Object, webSecurity.Object, - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == UrlProviderMode.Auto.ToString())), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), Enumerable.Empty(), globalSettings, - new TestVariationContextAccessor(), - true); //replace it + new TestVariationContextAccessor()); + + //replace it + umbracoContextAccessor.UmbracoContext = umbCtx; var urlHelper = new Mock(); urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); - var membershipHelper = new MembershipHelper(new TestUmbracoContextAccessor(umbCtx), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); + var membershipHelper = new MembershipHelper(umbCtx.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); - var umbHelper = new UmbracoHelper(umbCtx, - Mock.Of(), + var umbHelper = new UmbracoHelper(Mock.Of(), Mock.Of(), - Mock.Of(), + Mock.Of(), Mock.Of(), - membershipHelper, - serviceContext); + Mock.Of(), + membershipHelper); - return CreateController(controllerType, request, umbHelper); + return CreateController(controllerType, request, umbracoContextAccessor, umbHelper); } - protected abstract ApiController CreateController(Type controllerType, HttpRequestMessage msg, UmbracoHelper helper); + protected abstract ApiController CreateController(Type controllerType, HttpRequestMessage msg, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper); } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs index 5834415568..8c598281dd 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs @@ -15,9 +15,9 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { public class TestRunner { - private readonly Func _controllerFactory; + private readonly Func _controllerFactory; - public TestRunner(Func controllerFactory) + public TestRunner(Func controllerFactory) { _controllerFactory = controllerFactory; } @@ -74,7 +74,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); } - + return Tuple.Create(response, json); } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestStartup.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestStartup.cs index 95b5a3bfeb..0827a1f786 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestStartup.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestStartup.cs @@ -16,10 +16,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting /// public class TestStartup { - private readonly Func _controllerFactory; + private readonly Func _controllerFactory; private readonly Action _initialize; - public TestStartup(Action initialize, Func controllerFactory) + public TestStartup(Action initialize, Func controllerFactory) { _controllerFactory = controllerFactory; _initialize = initialize; diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index d7dcf8e79a..e93e8e8740 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -359,12 +359,12 @@ namespace Umbraco.Tests.TestHelpers.Entities contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar) { Alias = "singleLineText", Name = "Text String", Mandatory = false, SortOrder = 4, DataTypeId = -88 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext) { Alias = "multilineText", Name = "Multiple Text Strings", Mandatory = false, SortOrder = 5, DataTypeId = -89 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = "upload", Name = "Upload Field", Mandatory = false, SortOrder = 6, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar) { Alias = "label", Name = "Label", Mandatory = false, SortOrder = 7, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = "label", Name = "Label", Mandatory = false, SortOrder = 7, DataTypeId = -92 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DateTime, ValueStorageType.Date) { Alias = "dateTime", Name = "Date Time", Mandatory = false, SortOrder = 8, DataTypeId = -36 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ColorPicker, ValueStorageType.Nvarchar) { Alias = "colorPicker", Name = "Color Picker", Mandatory = false, SortOrder = 9, DataTypeId = -37 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DropDownListFlexible, ValueStorageType.Nvarchar) { Alias = "ddlMultiple", Name = "Dropdown List Multiple", Mandatory = false, SortOrder = 11, DataTypeId = -39 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.RadioButtonList, ValueStorageType.Nvarchar) { Alias = "rbList", Name = "Radio Button List", Mandatory = false, SortOrder = 12, DataTypeId = -40 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Date, ValueStorageType.Date) { Alias = "date", Name = "Date", Mandatory = false, SortOrder = 13, DataTypeId = -41 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DateTime, ValueStorageType.Date) { Alias = "date", Name = "Date", Mandatory = false, SortOrder = 13, DataTypeId = -36 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DropDownListFlexible, ValueStorageType.Integer) { Alias = "ddl", Name = "Dropdown List", Mandatory = false, SortOrder = 14, DataTypeId = -42 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.CheckBoxList, ValueStorageType.Nvarchar) { Alias = "chklist", Name = "Checkbox List", Mandatory = false, SortOrder = 15, DataTypeId = -43 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 }); @@ -420,10 +420,10 @@ namespace Umbraco.Tests.TestHelpers.Entities var contentCollection = new PropertyTypeCollection(false); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 97fffe11dd..572d572ab7 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -21,6 +21,7 @@ namespace Umbraco.Tests.TestHelpers settings.TimeOutInMinutes == 20 && settings.DefaultUILanguage == "en" && settings.LocalTempStorageLocation == LocalTempStorage.Default && + settings.LocalTempPath == IOHelper.MapPath("~/App_Data/TEMP") && settings.ReservedPaths == (GlobalSettings.StaticReservedPaths + "~/umbraco") && settings.ReservedUrls == GlobalSettings.StaticReservedUrls); return config; diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index 36c5961b9f..e27fe17bbe 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -17,17 +17,27 @@ namespace Umbraco.Tests.TestHelpers.Stubs /// internal class TestControllerFactory : IControllerFactory { - private readonly UmbracoContext _umbracoContext; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILogger _logger; + private readonly Func _factory; - public TestControllerFactory(UmbracoContext umbracoContext, ILogger logger) + public TestControllerFactory(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) { - _umbracoContext = umbracoContext; + _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; } + public TestControllerFactory(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, Func factory) + { + _umbracoContextAccessor = umbracoContextAccessor; + _logger = logger; + _factory = factory; + } + public IController CreateController(RequestContext requestContext, string controllerName) { + if (_factory != null) return _factory(requestContext); + var types = TypeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); @@ -38,7 +48,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs var possibleParams = new object[] { - _umbracoContext, _logger + _umbracoContextAccessor, _logger }; var ctors = t.GetConstructors(); foreach (var ctor in ctors.OrderByDescending(x => x.GetParameters().Length)) diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs index 0ffcfb7a3f..39cac6e24f 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs @@ -33,15 +33,20 @@ namespace Umbraco.Tests.TestHelpers.Stubs { if (_enabled == false) return; - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; - MiniProfiler.Start(); + //see https://miniprofiler.com/dotnet/AspDotNet + MiniProfiler.Configure(new MiniProfilerOptions + { + SqlFormatter = new SqlServerFormatter(), + StackMaxLength = 5000, + }); + + MiniProfiler.StartNew(); } public void Stop(bool discardResults = false) { if (_enabled) - MiniProfiler.Stop(discardResults); + MiniProfiler.Current.Stop(discardResults); } } } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 660d0f201e..0f84319976 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -116,11 +116,21 @@ namespace Umbraco.Tests.TestHelpers var umbracoSettings = GetUmbracoSettings(); var globalSettings = GetGlobalSettings(); - var webSecurity = new Mock(null, null, globalSettings).Object; var urlProviders = Enumerable.Empty(); if (accessor == null) accessor = new TestUmbracoContextAccessor(); - return UmbracoContext.EnsureContext(accessor, httpContext, publishedSnapshotService, webSecurity, umbracoSettings, urlProviders, globalSettings, new TestVariationContextAccessor(), true); + + var umbracoContextFactory = new UmbracoContextFactory( + accessor, + publishedSnapshotService, + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + umbracoSettings, + globalSettings, + urlProviders, + Mock.Of()); + + return umbracoContextFactory.EnsureUmbracoContext(httpContext).UmbracoContext; } public IUmbracoSettingsSection GetUmbracoSettings() @@ -143,7 +153,7 @@ namespace Umbraco.Tests.TestHelpers public IFileSystems GetFileSystemsMock() { var fileSystems = Mock.Of(); - + MockFs(fileSystems, x => x.MacroPartialsFileSystem); MockFs(fileSystems, x => x.MvcViewsFileSystem); MockFs(fileSystems, x => x.PartialViewsFileSystem); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 7a9702031b..110accdecf 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -153,7 +153,7 @@ namespace Umbraco.Tests.TestHelpers var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); var serverRegistrationService = GetLazyService(factory, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberGroupService = GetLazyService(factory, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); @@ -176,7 +176,7 @@ namespace Umbraco.Tests.TestHelpers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var compiledPackageXmlParser = new CompiledPackageXmlParser(new ConflictingPackageData(macroService.Value, fileService.Value)); return new PackagingService( - auditService.Value, + auditService.Value, new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders), logger, "createdPackages.config"), new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, @@ -188,9 +188,10 @@ namespace Umbraco.Tests.TestHelpers new DirectoryInfo(IOHelper.GetRootDirectorySafe()))); }); var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c))); - var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var contentTypeServiceBaseFactory = GetLazyService(factory, c => new ContentTypeBaseServiceProvider(factory.GetInstance(),factory.GetInstance(),factory.GetInstance())); return new ServiceContext( publicAccessService, @@ -217,7 +218,8 @@ namespace Umbraco.Tests.TestHelpers notificationService, externalLoginService, redirectUrlService, - consentService); + consentService, + contentTypeServiceBaseFactory); } private Lazy GetLazyService(IFactory container, Func ctor) diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 2bfc1b6dc8..643deab304 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -307,6 +307,13 @@ namespace Umbraco.Tests.TestHelpers var schemaHelper = new DatabaseSchemaCreator(scope.Database, Logger); //Create the umbraco database and its base data schemaHelper.InitializeDatabaseSchema(); + + //Special case, we need to create the xml cache tables manually since they are not part of the default + //setup. + //TODO: Remove this when we update all tests to use nucache + schemaHelper.CreateTable(); + schemaHelper.CreateTable(); + scope.Complete(); } diff --git a/src/Umbraco.Tests/Testing/ContentBaseExtensions.cs b/src/Umbraco.Tests/Testing/ContentBaseExtensions.cs index 58d4dfbd7f..d33818a31b 100644 --- a/src/Umbraco.Tests/Testing/ContentBaseExtensions.cs +++ b/src/Umbraco.Tests/Testing/ContentBaseExtensions.cs @@ -1,13 +1,17 @@ using System; using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Services; namespace Umbraco.Tests.Testing { public static class ContentBaseExtensions { + /// - /// Set property values by alias with an annonymous object. + /// Set property values by alias with an anonymous object. /// /// Does not support variants. public static void PropertyValues(this IContentBase content, object value, string culture = null, string segment = null) @@ -18,26 +22,12 @@ namespace Umbraco.Tests.Testing var propertyInfos = value.GetType().GetProperties(); foreach (var propertyInfo in propertyInfos) { - //Check if a PropertyType with alias exists thus being a valid property - var propertyType = content.PropertyTypes.FirstOrDefault(x => x.Alias == propertyInfo.Name); - if (propertyType == null) + if (!content.Properties.TryGetValue(propertyInfo.Name, out var property)) throw new Exception($"The property alias {propertyInfo.Name} is not valid, because no PropertyType with this alias exists"); - //Check if a Property with the alias already exists in the collection thus being updated or inserted - var item = content.Properties.FirstOrDefault(x => x.Alias == propertyInfo.Name); - if (item != null) - { - item.SetValue(propertyInfo.GetValue(value, null), culture, segment); - //Update item with newly added value - content.Properties.Add(item); - } - else - { - //Create new Property to add to collection - var property = propertyType.CreateProperty(); - property.SetValue(propertyInfo.GetValue(value, null), culture, segment); - content.Properties.Add(property); - } + property.SetValue(propertyInfo.GetValue(value, null), culture, segment); + //Update item with newly added value + content.Properties.Add(property); } } } diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 622186a547..57381eb287 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -16,6 +16,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; +using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; @@ -44,7 +45,7 @@ namespace Umbraco.Tests.Testing.TestingTests public void Can_Mock_Umbraco_Context() { var umbracoContext = TestObjects.GetUmbracoContextMock(Current.UmbracoContextAccessor); - Assert.AreEqual(umbracoContext, UmbracoContext.Current); + Assert.AreEqual(umbracoContext, Current.UmbracoContext); } [Test] @@ -60,13 +61,12 @@ namespace Umbraco.Tests.Testing.TestingTests Composition.Register(); // ReSharper disable once UnusedVariable - var helper = new UmbracoHelper(umbracoContext, - Mock.Of(), + var helper = new UmbracoHelper(Mock.Of(), Mock.Of(), - Mock.Of(), + Mock.Of(), Mock.Of(), - new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()), - ServiceContext.CreatePartial()); + Mock.Of(), + new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())); Assert.Pass(); } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 7c8d1100f8..fedc94d45b 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Reflection; using AutoMapper; using Examine; @@ -8,9 +7,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Components; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.Composers; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; @@ -26,7 +23,6 @@ using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; -using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Tests.Components; @@ -36,12 +32,15 @@ using Umbraco.Web; using Umbraco.Web.Services; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Actions; -using Umbraco.Web.Composing.Composers; using Umbraco.Web.ContentApps; using Umbraco.Web.PublishedCache; -using Current = Umbraco.Core.Composing.Current; using Umbraco.Web.Routing; using Umbraco.Web.Trees; +using Umbraco.Core.Composing.CompositionExtensions; +using Umbraco.Web.Composing.CompositionExtensions; +using Umbraco.Web.Sections; +using Current = Umbraco.Core.Composing.Current; +using FileSystems = Umbraco.Core.IO.FileSystems; namespace Umbraco.Tests.Testing { @@ -155,7 +154,7 @@ namespace Umbraco.Tests.Testing // etc ComposeWeb(); - ComposeWtf(); + ComposeMisc(); // not sure really Compose(Composition); @@ -220,17 +219,19 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); // register back office sections in the order we want them rendered - Composition.WithCollectionBuilder().Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); + Composition.WithCollectionBuilder().Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); Composition.RegisterUnique(); + } - protected virtual void ComposeWtf() + protected virtual void ComposeMisc() { // what else? var runtimeStateMock = new Mock(); @@ -242,7 +243,7 @@ namespace Umbraco.Tests.Testing Composition.WithCollectionBuilder(); Composition.RegisterUnique(); - Composition.RegisterUnique(); + Composition.RegisterUnique(); // register empty content apps collection Composition.WithCollectionBuilder(); @@ -280,7 +281,7 @@ namespace Umbraco.Tests.Testing // common to all tests = cannot be overriden private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return new TypeLoader(runtimeCache, globalSettings.LocalTempStorageLocation, logger, false) + return new TypeLoader(runtimeCache, globalSettings.LocalTempPath, logger, false) { AssembliesToScan = new[] { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 41c9581131..20cea53b09 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -78,40 +78,39 @@
- - - + + + - 1.8.9 + 1.8.14 - + - - - - - - - - - - + + + + + + + + + + - - + + - - - + + + - + - - + @@ -127,6 +126,8 @@ + + @@ -141,6 +142,8 @@ + + @@ -259,7 +262,7 @@ - + @@ -488,6 +491,7 @@ + diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 3019138809..f7b1799d63 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -70,7 +70,7 @@ namespace Umbraco.Tests.UmbracoExamine m.GetCultureName(It.IsAny()) == (string)x.Attribute("nodeName") && m.Path == (string)x.Attribute("path") && m.Properties == new PropertyCollection() && - m.ContentType == Mock.Of(mt => + m.ContentType == Mock.Of(mt => mt.Icon == "test" && mt.Alias == x.Name.LocalName && mt.Id == (int)x.Attribute("nodeType")))) @@ -110,7 +110,7 @@ namespace Umbraco.Tests.UmbracoExamine m.GetCultureName(It.IsAny()) == (string)x.Attribute("nodeName") && m.Path == (string)x.Attribute("path") && m.Properties == new PropertyCollection() && - m.ContentType == Mock.Of(mt => + m.ContentType == Mock.Of(mt => mt.Alias == (string)x.Attribute("nodeTypeAlias") && mt.Id == (int)x.Attribute("nodeType")))) .ToArray(); diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index ba6a83adff..9e59422310 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Models; using Newtonsoft.Json; using System.Collections.Generic; using System; +using Umbraco.Core; namespace Umbraco.Tests.UmbracoExamine { diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 7aa36f16e3..a45a33ec00 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -5,6 +5,7 @@ using Examine; using Examine.Search; using NUnit.Framework; using Moq; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; @@ -42,7 +43,7 @@ namespace Umbraco.Tests.UmbracoExamine m.Path == (string)x.Attribute("path") && m.Properties == new PropertyCollection() && m.Published == true && - m.ContentType == Mock.Of(mt => + m.ContentType == Mock.Of(mt => mt.Icon == "test" && mt.Alias == x.Name.LocalName && mt.Id == (int)x.Attribute("nodeType")))) diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index cf0bf689a1..a4213b4f0e 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -30,6 +30,11 @@ using System; using Umbraco.Web.WebApi; using Umbraco.Web.Trees; using System.Globalization; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; using Umbraco.Web.Actions; namespace Umbraco.Tests.Web.Controllers @@ -38,6 +43,14 @@ namespace Umbraco.Tests.Web.Controllers [UmbracoTest(Database = UmbracoTestOptions.Database.None)] public class ContentControllerTests : TestWithDatabaseBase { + private IContentType _contentTypeForMockedContent; + + public override void SetUp() + { + base.SetUp(); + _contentTypeForMockedContent = null; + } + protected override void ComposeApplication(bool withApplication) { base.ComposeApplication(withApplication); @@ -82,6 +95,7 @@ namespace Umbraco.Tests.Web.Controllers textService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())).Returns("text"); Composition.RegisterUnique(f => Mock.Of()); + Composition.RegisterUnique(f => Mock.Of()); Composition.RegisterUnique(f => userServiceMock.Object); Composition.RegisterUnique(f => entityService.Object); Composition.RegisterUnique(f => dataTypeService.Object); @@ -109,23 +123,42 @@ namespace Umbraco.Tests.Web.Controllers }; } - private IContent GetMockedContent() + private IContentType GetMockedContentType() { - var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType()); - content.Id = 123; - content.Path = "-1,123"; + var contentType = MockedContentTypes.CreateSimpleContentType(); //ensure things have ids var ids = 888; - foreach (var g in content.PropertyGroups) + foreach (var g in contentType.CompositionPropertyGroups) { g.Id = ids; ids++; } - foreach (var p in content.PropertyTypes) + foreach (var p in contentType.CompositionPropertyGroups) { p.Id = ids; ids++; } + + return contentType; + } + + private IContent GetMockedContent() + { + if (_contentTypeForMockedContent == null) + { + _contentTypeForMockedContent = GetMockedContentType(); + Mock.Get(Current.Services.ContentTypeService) + .Setup(x => x.Get(_contentTypeForMockedContent.Id)) + .Returns(_contentTypeForMockedContent); + Mock.Get(Current.Services.ContentTypeService) + .As() + .Setup(x => x.Get(_contentTypeForMockedContent.Id)) + .Returns(_contentTypeForMockedContent); + } + + var content = MockedContent.CreateSimpleContent(_contentTypeForMockedContent); + content.Id = 123; + content.Path = "-1,123"; return content; } @@ -206,17 +239,28 @@ namespace Umbraco.Tests.Web.Controllers [Test] public async Task PostSave_Validate_Existing_Content() { - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { var contentServiceMock = Mock.Get(Current.Services.ContentService); contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null); //do not find it var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - var usersController = new ContentController(propertyEditorCollection); - return usersController; + + var controller = new ContentController( + propertyEditorCollection, + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + + return controller; } - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, content: GetMultiPartRequestContent(PublishJsonInvariant), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), @@ -232,21 +276,28 @@ namespace Umbraco.Tests.Web.Controllers [Test] public async Task PostSave_Validate_At_Least_One_Variant_Flagged_For_Saving() { - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { - var contentServiceMock = Mock.Get(Current.Services.ContentService); - contentServiceMock.Setup(x => x.GetById(123)).Returns(() => GetMockedContent()); - var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - var usersController = new ContentController(propertyEditorCollection); - return usersController; + var controller = new ContentController( + propertyEditorCollection, + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + + return controller; } var json = JsonConvert.DeserializeObject(PublishJsonInvariant); //remove all save flaggs ((JArray)json["variants"])[0]["save"] = false; - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), @@ -263,14 +314,24 @@ namespace Umbraco.Tests.Web.Controllers [Test] public async Task PostSave_Validate_Properties_Exist() { - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { var contentServiceMock = Mock.Get(Current.Services.ContentService); contentServiceMock.Setup(x => x.GetById(123)).Returns(() => GetMockedContent()); var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - var usersController = new ContentController(propertyEditorCollection); - return usersController; + var controller = new ContentController( + propertyEditorCollection, + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + + return controller; } var json = JsonConvert.DeserializeObject(PublishJsonInvariant); @@ -283,7 +344,7 @@ namespace Umbraco.Tests.Web.Controllers value = "hello" })); - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), @@ -297,20 +358,29 @@ namespace Umbraco.Tests.Web.Controllers { var content = GetMockedContent(); - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { - var contentServiceMock = Mock.Get(Current.Services.ContentService); contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content); contentServiceMock.Setup(x => x.Save(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - var usersController = new ContentController(propertyEditorCollection); - return usersController; + var controller = new ContentController( + propertyEditorCollection, + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + + return controller; } - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, content: GetMultiPartRequestContent(PublishJsonInvariant), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), @@ -319,8 +389,6 @@ namespace Umbraco.Tests.Web.Controllers Assert.AreEqual(HttpStatusCode.OK, response.Item1.StatusCode); var display = JsonConvert.DeserializeObject(response.Item2); Assert.AreEqual(1, display.Variants.Count()); - Assert.AreEqual(content.PropertyGroups.Count(), display.Variants.ElementAt(0).Tabs.Count()); - Assert.AreEqual(content.PropertyTypes.Count(), display.Variants.ElementAt(0).Tabs.ElementAt(0).Properties.Count()); } [Test] @@ -328,24 +396,33 @@ namespace Umbraco.Tests.Web.Controllers { var content = GetMockedContent(); - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { - var contentServiceMock = Mock.Get(Current.Services.ContentService); contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content); contentServiceMock.Setup(x => x.Save(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - var usersController = new ContentController(propertyEditorCollection); - return usersController; + var controller = new ContentController( + propertyEditorCollection, + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + + return controller; } //clear out the name var json = JsonConvert.DeserializeObject(PublishJsonInvariant); json["variants"].ElementAt(0)["name"] = null; - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), @@ -363,24 +440,33 @@ namespace Umbraco.Tests.Web.Controllers { var content = GetMockedContent(); - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { - var contentServiceMock = Mock.Get(Current.Services.ContentService); contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content); contentServiceMock.Setup(x => x.Save(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - var usersController = new ContentController(propertyEditorCollection); - return usersController; + var controller = new ContentController( + propertyEditorCollection, + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + + return controller; } //clear out one of the names var json = JsonConvert.DeserializeObject(PublishJsonVariant); json["variants"].ElementAt(0)["name"] = null; - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), diff --git a/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs b/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs index 01525f12da..ed0c02b806 100644 --- a/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs @@ -53,8 +53,8 @@ namespace Umbraco.Tests.Web.Controllers [PluginController("Area1")] public class Plugin1Controller : PluginController { - public Plugin1Controller(UmbracoContext umbracoContext) - : base(umbracoContext, null, null, null, null, null) + public Plugin1Controller(IUmbracoContextAccessor umbracoContextAccessor) + : base(umbracoContextAccessor, null, null, null, null, null, null) { } } @@ -62,8 +62,8 @@ namespace Umbraco.Tests.Web.Controllers [PluginController("Area1")] public class Plugin2Controller : PluginController { - public Plugin2Controller(UmbracoContext umbracoContext) - : base(umbracoContext, null, null, null, null, null) + public Plugin2Controller(IUmbracoContextAccessor umbracoContextAccessor) + : base(umbracoContextAccessor, null, null, null, null, null, null) { } } @@ -71,16 +71,16 @@ namespace Umbraco.Tests.Web.Controllers [PluginController("Area2")] public class Plugin3Controller : PluginController { - public Plugin3Controller(UmbracoContext umbracoContext) - : base(umbracoContext, null, null, null, null, null) + public Plugin3Controller(IUmbracoContextAccessor umbracoContextAccessor) + : base(umbracoContextAccessor, null, null, null, null, null, null) { } } public class Plugin4Controller : PluginController { - public Plugin4Controller(UmbracoContext umbracoContext) - : base(umbracoContext, null, null, null, null, null) + public Plugin4Controller(IUmbracoContextAccessor umbracoContextAccessor) + : base(umbracoContextAccessor, null, null, null, null, null, null) { } } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 857e922ac9..c48aa2717c 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -7,9 +7,13 @@ using Moq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; @@ -49,7 +53,7 @@ namespace Umbraco.Tests.Web.Controllers [Test] public async System.Threading.Tasks.Task Save_User() { - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { //setup some mocks Umbraco.Core.Configuration.GlobalSettings.HasSmtpServer = true; @@ -68,7 +72,15 @@ namespace Umbraco.Tests.Web.Controllers userServiceMock.Setup(service => service.GetUserById(It.IsAny())) .Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List(), new int[0], new int[0]) : null); - var usersController = new UsersController(); + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); return usersController; } @@ -82,7 +94,7 @@ namespace Umbraco.Tests.Web.Controllers UserGroups = new[] { "writers" } }; - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "PostSaveUser", HttpMethod.Post, new ObjectContent(userSave, new JsonMediaTypeFormatter())); var obj = JsonConvert.DeserializeObject(response.Item2); @@ -122,15 +134,23 @@ namespace Umbraco.Tests.Web.Controllers [Test] public async System.Threading.Tasks.Task GetPagedUsers_Empty() { - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { - var usersController = new UsersController(); + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); return usersController; } MockForGetPagedUsers(); - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); var obj = JsonConvert.DeserializeObject>(response.Item2); @@ -140,7 +160,7 @@ namespace Umbraco.Tests.Web.Controllers [Test] public async System.Threading.Tasks.Task GetPagedUsers_10() { - ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) { //setup some mocks var userServiceMock = Mock.Get(Current.Services.UserService); @@ -151,13 +171,21 @@ namespace Umbraco.Tests.Web.Controllers It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(() => users); - var usersController = new UsersController(); + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); return usersController; } MockForGetPagedUsers(); - var runner = new TestRunner(Factory); + var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); var obj = JsonConvert.DeserializeObject>(response.Item2); diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs index ba19f41e74..a301dfbd04 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs @@ -7,6 +7,9 @@ namespace Umbraco.Tests.Web.Mvc [TestFixture] public class HtmlHelperExtensionMethodsTests { + private const string SampleWithAnchorElement = "Hello world, this is some text with a link"; + private const string SampleWithBoldAndAnchorElements = "Hello world, this is some text with a link"; + [SetUp] public virtual void Initialize() { @@ -29,5 +32,99 @@ namespace Umbraco.Tests.Web.Mvc var output = _htmlHelper.Wrap("div", "hello world", new {style = "color:red;", onclick = "void();"}); Assert.AreEqual("
hello world
", output.ToHtmlString()); } + + [Test] + public static void Truncate_Simple() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.Truncate(SampleWithAnchorElement, 25).ToString(); + + Assert.AreEqual("Hello world, this is some…", result); + } + + [Test] + public static void When_Truncating_A_String_Ends_With_A_Space_We_Should_Trim_The_Space_Before_Appending_The_Ellipsis() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.Truncate(SampleWithAnchorElement, 26).ToString(); + + Assert.AreEqual("Hello world, this is some…", result); + } + + [Test] + public static void Truncate_Inside_Word() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.Truncate(SampleWithAnchorElement, 24).ToString(); + + Assert.AreEqual("Hello world, this is som…", result); + } + + [Test] + public static void Truncate_With_Tag() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.Truncate(SampleWithAnchorElement, 35).ToString(); + + Assert.AreEqual("Hello world, this is some text with…", result); + } + + [Test] + public static void Truncate_By_Words() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.TruncateByWords(SampleWithAnchorElement, 4).ToString(); + + Assert.AreEqual("Hello world, this is…", result); + } + + [Test] + public static void Truncate_By_Words_With_Tag() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.TruncateByWords(SampleWithBoldAndAnchorElements, 4).ToString(); + + Assert.AreEqual("Hello world, this is…", result); + } + + [Test] + public static void Truncate_By_Words_Mid_Tag() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.TruncateByWords(SampleWithAnchorElement, 7).ToString(); + + Assert.AreEqual("Hello world, this is some text with…", result); + } + + [Test] + public static void Strip_All_Html() + { + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.StripHtml(SampleWithBoldAndAnchorElements, null).ToString(); + + Assert.AreEqual("Hello world, this is some text with a link", result); + } + + [Test] + public static void Strip_Specific_Html() + { + string[] tags = { "b" }; + + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.StripHtml(SampleWithBoldAndAnchorElements, tags).ToString(); + + Assert.AreEqual(SampleWithAnchorElement, result); + } + + [Test] + public static void Strip_Invalid_Html() + { + const string text = "Hello world, is some text with a link"; + + var helper = new HtmlHelper(new ViewContext(), new ViewPage()); + var result = helper.StripHtml(text).ToString(); + + Assert.AreEqual("Hello world, is some text with a link", result); + } } } diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index 29deeb31ec..b41aca21c3 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -63,20 +63,22 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - //var appCtx = new ApplicationContext( - // CacheHelper.CreateDisabledCacheHelper(), - // new ProfilingLogger(Mock.Of(), Mock.Of())); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); - var ctrl = new MatchesDefaultIndexController { UmbracoContext = umbCtx }; + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbCtx); + var ctrl = new MatchesDefaultIndexController { UmbracoContextAccessor = umbracoContextAccessor }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); @@ -90,17 +92,22 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); - var ctrl = new MatchesOverriddenIndexController { UmbracoContext = umbCtx }; + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbCtx); + var ctrl = new MatchesOverriddenIndexController { UmbracoContextAccessor = umbracoContextAccessor }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); @@ -114,17 +121,22 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); - var ctrl = new MatchesCustomIndexController { UmbracoContext = umbCtx }; + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbCtx); + var ctrl = new MatchesCustomIndexController { UmbracoContextAccessor = umbracoContextAccessor }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); @@ -138,17 +150,22 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); - var ctrl = new MatchesAsyncIndexController { UmbracoContext = umbCtx }; + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbCtx); + var ctrl = new MatchesAsyncIndexController { UmbracoContextAccessor = umbracoContextAccessor }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index ec37793c10..846ee4b7d8 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -7,14 +7,12 @@ using System.Web.Security; using Moq; using NUnit.Framework; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; @@ -40,18 +38,23 @@ namespace Umbraco.Tests.Web.Mvc public void Can_Construct_And_Get_Result() { var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = UmbracoContext.EnsureContext( - Current.UmbracoContextAccessor, - new Mock().Object, - Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, - new TestVariationContextAccessor(), - true); - var ctrl = new TestSurfaceController(umbracoContext); + var umbracoContextFactory = new UmbracoContextFactory( + Current.UmbracoContextAccessor, + Mock.Of(), + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbracoContext = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); + + var ctrl = new TestSurfaceController(umbracoContextAccessor); var result = ctrl.Index(); @@ -62,83 +65,65 @@ namespace Umbraco.Tests.Web.Mvc public void Umbraco_Context_Not_Null() { var globalSettings = TestObjects.GetGlobalSettings(); - var umbCtx = UmbracoContext.EnsureContext( - Current.UmbracoContextAccessor, - new Mock().Object, - Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, - new TestVariationContextAccessor(), - true); - var ctrl = new TestSurfaceController(umbCtx); + var umbracoContextFactory = new UmbracoContextFactory( + Current.UmbracoContextAccessor, + Mock.Of(), + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbCtx); + + var ctrl = new TestSurfaceController(umbracoContextAccessor); Assert.IsNotNull(ctrl.UmbracoContext); } - [Test] - public void Umbraco_Helper_Not_Null() - { - var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = UmbracoContext.EnsureContext( - Current.UmbracoContextAccessor, - new Mock().Object, - Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, - new TestVariationContextAccessor(), - true); - - var controller = new TestSurfaceController(umbracoContext); - Composition.Register(_ => umbracoContext); - - Assert.IsNotNull(controller.Umbraco); - } - [Test] public void Can_Lookup_Content() { var publishedSnapshot = new Mock(); publishedSnapshot.Setup(x => x.Members).Returns(Mock.Of()); - var contentCache = new Mock(); var content = new Mock(); content.Setup(x => x.Id).Returns(2); - contentCache.Setup(x => x.GetById(It.IsAny())).Returns(content.Object); - var mediaCache = new Mock(); - publishedSnapshot.Setup(x => x.Content).Returns(contentCache.Object); - publishedSnapshot.Setup(x => x.Media).Returns(mediaCache.Object); var publishedSnapshotService = new Mock(); - publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot.Object); var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = UmbracoContext.EnsureContext( + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - new Mock().Object, publishedSnapshotService.Object, - new Mock(null, null, globalSettings).Object, - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbracoContext = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var helper = new UmbracoHelper( - umbracoContext, - Mock.Of(), + content.Object, Mock.Of(), - Mock.Of(), + Mock.Of(), Mock.Of(), - new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()), - ServiceContext.CreatePartial()); + Mock.Of(query => query.Content(2) == content.Object), + new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())); - var ctrl = new TestSurfaceController(umbracoContext, helper); + var ctrl = new TestSurfaceController(umbracoContextAccessor, helper); var result = ctrl.GetContent(2) as PublishedContentResult; Assert.IsNotNull(result); + Assert.IsNotNull(result.Content); Assert.AreEqual(2, result.Content.Id); } @@ -148,16 +133,20 @@ namespace Umbraco.Tests.Web.Mvc var webRoutingSettings = Mock.Of(section => section.UrlProviderMode == "Auto"); var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = UmbracoContext.EnsureContext( + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - new Mock().Object, Mock.Of(), - new Mock(null, null, globalSettings).Object, - Mock.Of(section => section.WebRouting == webRoutingSettings), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == webRoutingSettings), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbracoContext = umbracoContextReference.UmbracoContext; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var content = Mock.Of(publishedContent => publishedContent.Id == 12345); @@ -174,7 +163,7 @@ namespace Umbraco.Tests.Web.Mvc var routeData = new RouteData(); routeData.DataTokens.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); - var ctrl = new TestSurfaceController(umbracoContext, new UmbracoHelper()); + var ctrl = new TestSurfaceController(umbracoContextAccessor, new UmbracoHelper()); ctrl.ControllerContext = new ControllerContext(contextBase, routeData, ctrl); var result = ctrl.GetContentFromCurrentPage() as PublishedContentResult; @@ -184,13 +173,9 @@ namespace Umbraco.Tests.Web.Mvc public class TestSurfaceController : SurfaceController { - public TestSurfaceController(UmbracoContext ctx, UmbracoHelper helper = null) - : base(ctx, null, ServiceContext.CreatePartial(), Mock.Of(), null, null) + public TestSurfaceController(IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper = null) + : base(umbracoContextAccessor, null, ServiceContext.CreatePartial(), Mock.Of(), null, null, helper) { - if (helper != null) - { - Umbraco = helper; - } } public ActionResult Index() diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index c5c8b5c77d..d1395c6f2e 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -5,6 +5,7 @@ using System.Web.Mvc; using System.Web.Routing; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index aa44e7d085..8a4e3e515b 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -19,6 +19,7 @@ using Umbraco.Web.Routing; using Umbraco.Web.Security; using Umbraco.Web.Templates; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; namespace Umbraco.Tests.Web { @@ -39,7 +40,7 @@ namespace Umbraco.Tests.Web // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var factory = new Mock(); factory.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NoAppCache.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); factory.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); var settings = SettingsForTests.GetDefaultUmbracoSettings(); @@ -96,20 +97,19 @@ namespace Umbraco.Tests.Web var snapshotService = Mock.Of(); Mock.Get(snapshotService).Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot); - using (var umbCtx = UmbracoContext.EnsureContext( + var umbracoContextFactory = new UmbracoContextFactory( Umbraco.Web.Composing.Current.UmbracoContextAccessor, - Mock.Of(), snapshotService, - new Mock(null, null, globalSettings).Object, - //setup a quick mock of the WebRouting section - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), - //pass in the custom url provider - new[]{ testUrlProvider.Object }, - globalSettings, new TestVariationContextAccessor(), - true)) + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), + globalSettings, + new[] { testUrlProvider.Object }, + Mock.Of()); + + using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) { - var output = TemplateUtilities.ParseInternalLinks(input, umbCtx.UrlProvider); + var output = TemplateUtilities.ParseInternalLinks(input, reference.UmbracoContext.UrlProvider); Assert.AreEqual(result, output); } diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs similarity index 59% rename from src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs rename to src/Umbraco.Tests/Web/UmbracoHelperTests.cs index 1c0b407ac6..b23b5bd6b7 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -1,183 +1,30 @@ using System; -using System.Collections.Generic; using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Tests.TestHelpers; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Tests.TestHelpers; using Umbraco.Web; -namespace Umbraco.Tests.FrontEnd +namespace Umbraco.Tests.Web { [TestFixture] public class UmbracoHelperTests - { - private const string SampleWithAnchorElement = "Hello world, this is some text with a link"; - private const string SampleWithBoldAndAnchorElements = "Hello world, this is some text with a link"; + { [TearDown] public void TearDown() { Current.Reset(); } - [Test] - public static void Truncate_Simple() - { - var helper = new UmbracoHelper(); - - var result = helper.Truncate(SampleWithAnchorElement, 25).ToString(); - - Assert.AreEqual("Hello world, this is some…", result); - } - - [Test] - public static void When_Truncating_A_String_Ends_With_A_Space_We_Should_Trim_The_Space_Before_Appending_The_Ellipsis() - { - var helper = new UmbracoHelper(); - - var result = helper.Truncate(SampleWithAnchorElement, 26).ToString(); - - Assert.AreEqual("Hello world, this is some…", result); - } - - [Test] - public static void Truncate_Inside_Word() - { - var helper = new UmbracoHelper(); - - var result = helper.Truncate(SampleWithAnchorElement, 24).ToString(); - - Assert.AreEqual("Hello world, this is som…", result); - } - - [Test] - public static void Truncate_With_Tag() - { - var helper = new UmbracoHelper(); - - var result = helper.Truncate(SampleWithAnchorElement, 35).ToString(); - - Assert.AreEqual("Hello world, this is some text with…", result); - } - - [Test] - public static 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(); - - const string expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; - - Assert.AreEqual(expectedResult, result); - } - - [Test] - public static 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(); - - const string expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; - - Assert.AreEqual(expectedResult, result); - } - - [Test] - public static void Truncate_By_Words() - { - var helper = new UmbracoHelper(); - - var result = helper.TruncateByWords(SampleWithAnchorElement, 4).ToString(); - - Assert.AreEqual("Hello world, this is…", result); - } - - [Test] - public static void Truncate_By_Words_With_Tag() - { - var helper = new UmbracoHelper(); - - var result = helper.TruncateByWords(SampleWithBoldAndAnchorElements, 4).ToString(); - - Assert.AreEqual("Hello world, this is…", result); - } - - [Test] - public static void Truncate_By_Words_Mid_Tag() - { - var helper = new UmbracoHelper(); - - var result = helper.TruncateByWords(SampleWithAnchorElement, 7).ToString(); - - Assert.AreEqual("Hello world, this is some text with…", result); - } - - [Test] - public static void Strip_All_Html() - { - var helper = new UmbracoHelper(); - - var result = helper.StripHtml(SampleWithBoldAndAnchorElements, null).ToString(); - - Assert.AreEqual("Hello world, this is some text with a link", result); - } - - [Test] - public static void Strip_Specific_Html() - { - string[] tags = { "b" }; - - var helper = new UmbracoHelper(); - - var result = helper.StripHtml(SampleWithBoldAndAnchorElements, tags).ToString(); - - Assert.AreEqual(SampleWithAnchorElement, result); - } - - [Test] - public static void Strip_Invalid_Html() - { - const string text = "Hello world, is some text with a link"; - - var helper = new UmbracoHelper(); - - var result = helper.StripHtml(text).ToString(); - - Assert.AreEqual("Hello world, is some text with a link", result); - } + + // ------- Int32 conversion tests [Test] public static void Converting_Boxed_34_To_An_Int_Returns_34() @@ -414,7 +261,7 @@ namespace Umbraco.Tests.FrontEnd .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( NoAppCache.Instance, - LocalTempStorage.Default, + IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()) ) ); diff --git a/src/Umbraco.Tests/Web/UrlHelperExtensionTests.cs b/src/Umbraco.Tests/Web/UrlHelperExtensionTests.cs new file mode 100644 index 0000000000..a4b96ab4ff --- /dev/null +++ b/src/Umbraco.Tests/Web/UrlHelperExtensionTests.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Web; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class UrlHelperExtensionTests + { + [Test] + public static void Create_Encrypted_RouteString_From_Anonymous_Object() + { + var additionalRouteValues = new + { + key1 = "value1", + key2 = "value2", + Key3 = "Value3", + keY4 = "valuE4" + }; + + var encryptedRouteString = UrlHelperRenderExtensions.CreateEncryptedRouteString( + "FormController", + "FormAction", + "", + additionalRouteValues + ); + + var result = encryptedRouteString.DecryptWithMachineKey(); + + const string expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; + + Assert.AreEqual(expectedResult, result); + } + + [Test] + public static void Create_Encrypted_RouteString_From_Dictionary() + { + var additionalRouteValues = new Dictionary() + { + {"key1", "value1"}, + {"key2", "value2"}, + {"Key3", "Value3"}, + {"keY4", "valuE4"} + }; + + var encryptedRouteString = UrlHelperRenderExtensions.CreateEncryptedRouteString( + "FormController", + "FormAction", + "", + additionalRouteValues + ); + + var result = encryptedRouteString.DecryptWithMachineKey(); + + const string expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; + + Assert.AreEqual(expectedResult, result); + } + } +} diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/build.js b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js index 4e519c58e9..32bf71f5f7 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/build.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js @@ -6,5 +6,5 @@ var runSequence = require('run-sequence'); // Build - build the files ready for production gulp.task('build', function(cb) { - runSequence(["dependencies", "js", "less", "views"], cb); + runSequence(["js", "dependencies", "less", "views"], "test:unit", cb); }); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js index e40d4c436a..eae65613c4 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js @@ -3,15 +3,21 @@ var config = require('../config'); var gulp = require('gulp'); var karmaServer = require('karma').Server; +var runSequence = require('run-sequence'); /************************** * Build tests **************************/ // Karma test -gulp.task('test:unit', function() { +gulp.task('runTests', function(cb) { + runSequence("js", "test:unit", cb); +}); + +gulp.task('test:unit', function () { + new karmaServer({ - configFile: __dirname + "/test/config/karma.conf.js", + configFile: __dirname + "/../../test/config/karma.conf.js", keepalive: true }) .start(); @@ -19,7 +25,7 @@ gulp.task('test:unit', function() { gulp.task('test:e2e', function() { new karmaServer({ - configFile: __dirname + "/test/config/e2e.js", + configFile: __dirname + "/../../test/config/e2e.js", keepalive: true }) .start(); diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css index 9259d26dca..0f4cb1dc75 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css @@ -35,9 +35,7 @@ display: block; width: 100%; min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .hidden { @@ -208,9 +206,7 @@ min-height: 30px; margin-left: 2.564102564102564%; *margin-left: 2.5109110747408616%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .row-fluid [class*="span"]:first-child { margin-left: 0; @@ -555,9 +551,7 @@ min-height: 30px; margin-left: 2.7624309392265194%; *margin-left: 2.709239449864817%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .row-fluid [class*="span"]:first-child { margin-left: 0; @@ -819,16 +813,12 @@ float: none; width: 100%; margin-left: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .span12, .row-fluid .span12 { width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .input-large, .input-xlarge, @@ -840,9 +830,7 @@ display: block; width: 100%; min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .input-prepend input, .input-append input, @@ -869,7 +857,7 @@ @media (max-width: 480px) { .nav-collapse { - -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0,0,0); } .page-header h1 small { display: block; @@ -961,16 +949,12 @@ padding: 9px 15px; font-weight: bold; color: #777777; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; + border-radius: 3px; } .nav-collapse .btn { padding: 4px 10px 4px; font-weight: normal; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; + border-radius: 4px; } .nav-collapse .dropdown-menu li + li a { margin-bottom: 2px; @@ -998,12 +982,8 @@ margin: 0 15px; background-color: transparent; border: none; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; + border-radius: 0; + box-shadow: none; } .nav-collapse .dropdown-menu:before, .nav-collapse .dropdown-menu:after { @@ -1023,9 +1003,7 @@ margin: 10px 0; border-top: 1px solid #f2f2f2; border-bottom: 1px solid #f2f2f2; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); } .navbar-inverse .nav-collapse .navbar-form, .navbar-inverse .nav-collapse .navbar-search { diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.cosmo.min.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.cosmo.min.css deleted file mode 100644 index f92b1605d4..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.cosmo.min.css +++ /dev/null @@ -1,20 +0,0 @@ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans') - , url(../../../font/opensans/OpenSans-Regular.woff) format('woff') - , url(../../../font/opensans/OpenSans-Regular.ttf) format('ttf'); -} - - -/*import url('//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700');! - - * Bootstrap v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Open Sans",Calibri,Candara,Arial,sans-serif;font-size:14px;line-height:20px;color:#555;background-color:#fff}a{color:#007fff;text-decoration:none}a:hover,a:focus{color:#06c;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#dfdfdf}a.muted:hover,a.muted:focus{color:#c6c6c6}.text-warning{color:#fff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-error{color:#fff}a.text-error:hover,a.text-error:focus{color:#e6e6e6}.text-info{color:#fff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-success{color:#fff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:300;line-height:20px;color:#080808;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#dfdfdf}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #dfdfdf}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#dfdfdf}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#999;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#dfdfdf}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Open Sans",Calibri,Candara,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#bbb;vertical-align:middle;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #bbb;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #bbb}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#dfdfdf;cursor:not-allowed;background-color:#fcfcfc;border-color:#bbb;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#bbb}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#bbb}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#bbb}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#fff}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#fff}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#fff;background-color:#ff7518;border-color:#fff}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#fff}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#fff}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#fff;background-color:#ff0039;border-color:#fff}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#fff}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#fff}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#fff;background-color:#3fb618;border-color:#fff}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#fff}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#fff}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#fff;background-color:#9954bb;border-color:#fff}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#7b7b7b}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#96ed7a;border-color:#3fb618}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#e8f8fd}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#3fb618}.table tbody tr.error>td{background-color:#ff0039}.table tbody tr.warning>td{background-color:#ff7518}.table tbody tr.info>td{background-color:#9954bb}.table-hover tbody tr.success:hover>td{background-color:#379f15}.table-hover tbody tr.error:hover>td{background-color:#e60033}.table-hover tbody tr.warning:hover>td{background-color:#fe6600}.table-hover tbody tr.info:hover>td{background-color:#8d46b0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#dfdfdf}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#eee;border:1px solid #dcdcdc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well-small{padding:9px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#999;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#dfdfdf;*background-color:#c8c8c8;background-image:-moz-linear-gradient(top,#eee,#c8c8c8);background-image:-webkit-gradient(linear,0 0,0 100%,from(#eee),to(#c8c8c8));background-image:-webkit-linear-gradient(top,#eee,#c8c8c8);background-image:-o-linear-gradient(top,#eee,#c8c8c8);background-image:linear-gradient(to bottom,#eee,#c8c8c8);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#c8c8c8 #c8c8c8 #a2a2a2;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffeeeeee',endColorstr='#ffc8c8c8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#999;background-color:#c8c8c8;*background-color:#bbb}.btn:active,.btn.active{background-color:#aeaeae \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#999;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:22px 30px;font-size:17.5px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:10.5px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0f82f5;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#1a8cff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#1a8cff),to(#0072e6));background-image:-webkit-linear-gradient(top,#1a8cff,#0072e6);background-image:-o-linear-gradient(top,#1a8cff,#0072e6);background-image:linear-gradient(to bottom,#1a8cff,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1a8cff',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.btn-primary:active,.btn-primary.active{background-color:#0059b3 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#fe781e;*background-color:#fe6600;background-image:-moz-linear-gradient(top,#ff8432,#fe6600);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff8432),to(#fe6600));background-image:-webkit-linear-gradient(top,#ff8432,#fe6600);background-image:-o-linear-gradient(top,#ff8432,#fe6600);background-image:linear-gradient(to bottom,#ff8432,#fe6600);background-repeat:repeat-x;border-color:#fe6600 #fe6600 #b14700;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff8432',endColorstr='#fffe6600',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#fe6600;*background-color:#e45c00}.btn-warning:active,.btn-warning.active{background-color:#cb5200 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#f50f43;*background-color:#e60033;background-image:-moz-linear-gradient(top,#ff1a4d,#e60033);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff1a4d),to(#e60033));background-image:-webkit-linear-gradient(top,#ff1a4d,#e60033);background-image:-o-linear-gradient(top,#ff1a4d,#e60033);background-image:linear-gradient(to bottom,#ff1a4d,#e60033);background-repeat:repeat-x;border-color:#e60033 #e60033 #902;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff1a4d',endColorstr='#ffe60033',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#e60033;*background-color:#cc002e}.btn-danger:active,.btn-danger.active{background-color:#b30028 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#41bb19;*background-color:#379f15;background-image:-moz-linear-gradient(top,#47cd1b,#379f15);background-image:-webkit-gradient(linear,0 0,0 100%,from(#47cd1b),to(#379f15));background-image:-webkit-linear-gradient(top,#47cd1b,#379f15);background-image:-o-linear-gradient(top,#47cd1b,#379f15);background-image:linear-gradient(to bottom,#47cd1b,#379f15);background-repeat:repeat-x;border-color:#379f15 #379f15 #205c0c;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff47cd1b',endColorstr='#ff379f15',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#379f15;*background-color:#2f8912}.btn-success:active,.btn-success.active{background-color:#28720f \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#9b59bb;*background-color:#8d46b0;background-image:-moz-linear-gradient(top,#a466c2,#8d46b0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#a466c2),to(#8d46b0));background-image:-webkit-linear-gradient(top,#a466c2,#8d46b0);background-image:-o-linear-gradient(top,#a466c2,#8d46b0);background-image:linear-gradient(to bottom,#a466c2,#8d46b0);background-repeat:repeat-x;border-color:#8d46b0 #8d46b0 #613079;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa466c2',endColorstr='#ff8d46b0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#8d46b0;*background-color:#7e3f9d}.btn-info:active,.btn-info.active{background-color:#6f378b \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#080808;*background-color:#000;background-image:-moz-linear-gradient(top,#0d0d0d,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0d0d0d),to(#000));background-image:-webkit-linear-gradient(top,#0d0d0d,#000);background-image:-o-linear-gradient(top,#0d0d0d,#000);background-image:linear-gradient(to bottom,#0d0d0d,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0d0d0d',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#000;*background-color:#000}.btn-inverse:active,.btn-inverse.active{background-color:#000 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#007fff;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#06c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#999;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#c8c8c8}.btn-group.open .btn-primary.dropdown-toggle{background-color:#0072e6}.btn-group.open .btn-warning.dropdown-toggle{background-color:#fe6600}.btn-group.open .btn-danger.dropdown-toggle{background-color:#e60033}.btn-group.open .btn-success.dropdown-toggle{background-color:#379f15}.btn-group.open .btn-info.dropdown-toggle{background-color:#8d46b0}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#000}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#ff7518;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert,.alert h4{color:#fff}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#fff;background-color:#3fb618;border-color:transparent}.alert-success h4{color:#fff}.alert-danger,.alert-error{color:#fff;background-color:#ff0039;border-color:transparent}.alert-danger h4,.alert-error h4{color:#fff}.alert-info{color:#fff;background-color:#9954bb;border-color:transparent}.alert-info h4{color:#fff}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#dfdfdf;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#007fff}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#bbb;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#007fff}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#007fff;border-bottom-color:#007fff}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#06c;border-bottom-color:#06c}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#bbb;border-bottom-color:#bbb}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#dfdfdf;border-color:#dfdfdf}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#dfdfdf}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#dfdfdf}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:50px;padding-right:20px;padding-left:20px;background-color:#080808;background-image:-moz-linear-gradient(top,#080808,#080808);background-image:-webkit-gradient(linear,0 0,0 100%,from(#080808),to(#080808));background-image:-webkit-linear-gradient(top,#080808,#080808);background-image:-o-linear-gradient(top,#080808,#080808);background-image:linear-gradient(to bottom,#080808,#080808);background-repeat:repeat-x;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808',endColorstr='#ff080808',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:15px 20px 15px;margin-left:-20px;font-size:20px;font-weight:200;color:#fff;text-shadow:0 1px 0 #080808}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:50px;color:#fff}.navbar-link{color:#fff}.navbar-link:hover,.navbar-link:focus{color:#bbb}.navbar .divider-vertical{height:50px;margin:0 9px;border-right:1px solid #080808;border-left:1px solid #080808}.navbar .btn,.navbar .btn-group{margin-top:10px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:10px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:10px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Open Sans",Calibri,Candara,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:15px 15px 15px;color:#fff;text-decoration:none;text-shadow:0 1px 0 #080808}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#bbb;text-decoration:none;background-color:rgba(0,0,0,0.05)}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#fff;text-decoration:none;background-color:transparent;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#000;*background-color:#000;background-image:-moz-linear-gradient(top,#000,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#000),to(#000));background-image:-webkit-linear-gradient(top,#000,#000);background-image:-o-linear-gradient(top,#000,#000);background-image:linear-gradient(to bottom,#000,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff000000',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#000;*background-color:#000}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#000 \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#bbb;border-bottom-color:#bbb}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:transparent}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#007fff;background-image:-moz-linear-gradient(top,#007fff,#007fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#007fff));background-image:-webkit-linear-gradient(top,#007fff,#007fff);background-image:-o-linear-gradient(top,#007fff,#007fff);background-image:linear-gradient(to bottom,#007fff,#007fff);background-repeat:repeat-x;border-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff007fff',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#fff}.navbar-inverse .navbar-text{color:#fff}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:rgba(0,0,0,0.05)}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#007fff}.navbar-inverse .navbar-link{color:#fff}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#007fff;border-left-color:#007fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#007fff}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#80bfff;border-color:#007fff;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#999;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0072e6;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#0072e6,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0072e6),to(#0072e6));background-image:-webkit-linear-gradient(top,#0072e6,#0072e6);background-image:-o-linear-gradient(top,#0072e6,#0072e6);background-image:linear-gradient(to bottom,#0072e6,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0072e6',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#0059b3 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#dfdfdf}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#dfdfdf;border:1px solid transparent;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#007fff}.pagination ul>.active>a,.pagination ul>.active>span{color:#dfdfdf;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#dfdfdf;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:22px 30px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:2px 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#dfdfdf;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#ff7518;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#ff7518;border-bottom:1px solid #fe6600;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:16px}.popover .arrow:after{border-width:15px;content:""}.popover.top .arrow{bottom:-16px;left:50%;margin-left:-16px;border-top-color:#999;border-top-color:transparent;border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-15px;border-top-color:#ff7518;border-bottom-width:0}.popover.right .arrow{top:50%;left:-16px;margin-top:-16px;border-right-color:#999;border-right-color:transparent;border-left-width:0}.popover.right .arrow:after{bottom:-15px;left:1px;border-right-color:#ff7518;border-left-width:0}.popover.bottom .arrow{top:-16px;left:50%;margin-left:-16px;border-bottom-color:#999;border-bottom-color:transparent;border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-15px;border-bottom-color:#ff7518;border-top-width:0}.popover.left .arrow{top:50%;right:-16px;margin-top:-16px;border-left-color:#999;border-left-color:transparent;border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-15px;border-left-color:#ff7518;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#007fff;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#bbb}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#dfdfdf}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#fff}.label-important[href],.badge-important[href]{background-color:#e6e6e6}.label-warning,.badge-warning{background-color:#ff7518}.label-warning[href],.badge-warning[href]{background-color:#e45c00}.label-success,.badge-success{background-color:#fff}.label-success[href],.badge-success[href]{background-color:#e6e6e6}.label-info,.badge-info{background-color:#fff}.label-info[href],.badge-info[href]{background-color:#e6e6e6}.label-inverse,.badge-inverse{background-color:#999}.label-inverse[href],.badge-inverse[href]{background-color:#808080}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#ff9046;background-image:-moz-linear-gradient(top,#ffa365,#ff7518);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ffa365),to(#ff7518));background-image:-webkit-linear-gradient(top,#ffa365,#ff7518);background-image:-o-linear-gradient(top,#ffa365,#ff7518);background-image:linear-gradient(to bottom,#ffa365,#ff7518);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffa365',endColorstr='#ffff7518',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#ffa365;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#080808;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#999;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}body{font-weight:300}h1{font-size:50px}h2,h3{font-size:26px}h4{font-size:14px}h5,h6{font-size:11px}blockquote{padding:10px 15px;background-color:#eee;border-left-color:#bbb}blockquote.pull-right{padding:10px 15px;border-right-color:#bbb}blockquote small{color:#bbb}.muted{color:#bbb}.text-warning{color:#ff7518}a.text-warning:hover{color:#e45c00}.text-error{color:#ff0039}a.text-error:hover{color:#cc002e}.text-info{color:#9954bb}a.text-info:hover{color:#7e3f9d}.text-success{color:#3fb618}a.text-success:hover{color:#2f8912}.navbar .navbar-inner{background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .brand:hover{color:#bbb}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{background-color:rgba(0,0,0,0.05);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle:hover,.navbar .nav li.dropdown.active>.dropdown-toggle:hover,.navbar .nav li.dropdown.open.active>.dropdown-toggle:hover{color:#eee}.navbar .navbar-search .search-query{line-height:normal}.navbar-inverse .brand,.navbar-inverse .nav>li>a{text-shadow:none}.navbar-inverse .brand:hover,.navbar-inverse .nav>.active>a,.navbar-inverse .nav>.active>a:hover,.navbar-inverse .nav>.active>a:focus{color:#fff;background-color:rgba(0,0,0,0.05);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar-inverse .navbar-search .search-query{color:#080808}div.subnav{margin:0 1px;background:#dfdfdf none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav .nav{background-color:transparent}div.subnav .nav>li>a{border-color:transparent}div.subnav .nav>.active>a,div.subnav .nav>.active>a:hover{color:#fff;background-color:#000;border-color:transparent;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav-fixed{top:51px;margin:0}.nav .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#007fff}.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li>a:hover{color:#fff;background-color:#007fff}.nav-tabs.nav-stacked>.active>a,.nav-tabs.nav-stacked>.active>a:hover{color:#bbb;background-color:#fff}.nav-tabs.nav-stacked>li:first-child>a,.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tabs-below>.nav-tabs>li>a,.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a{color:#000;background-color:#dfdfdf;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a:hover{color:#fff;background-color:#000}.nav-pills>.disabled>a,.nav-pills>.disabled>a:hover{color:#999;background-color:#eee}.nav-list>li>a{color:#080808}.nav-list>li>a:hover{color:#fff;text-shadow:none;background-color:#007fff}.nav-list .nav-header{color:#080808}.nav-list .divider{background-color:#bbb;border-bottom:0}.pagination ul{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.pagination ul>li>a,.pagination ul>li>span{margin-right:6px;color:#080808}.pagination ul>li>a:hover,.pagination ul>li>span:hover{color:#fff;background-color:#080808}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{margin-right:0}.pagination ul>.active>a,.pagination ul>.active>span{color:#fff}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;background-color:#eee}.pager li>a,.pager li>span{color:#080808;background-color:#dfdfdf;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.pager li>a:hover,.pager li>span:hover{color:#fff;background-color:#080808}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;background-color:#eee}.breadcrumb{background-color:#dfdfdf}.breadcrumb li{text-shadow:none}.breadcrumb .divider,.breadcrumb .active{color:#080808;text-shadow:none}.btn{padding:5px 12px;text-shadow:none;background-image:none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn.disabled{box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-large{padding:22px 30px}.btn-small{padding:2px 10px}.btn-mini{padding:2px 6px}.btn-group>.btn:first-child,.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.dropdown-toggle{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.table tbody tr.success td{color:#fff}.table tbody tr.error td{color:#fff}.table tbody tr.info td{color:#fff}.table-bordered{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{color:#080808}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#ff7518}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#080808;border-color:#ff7518}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#ff0039}.control-group.error input,.control-group.error select,.control-group.error textarea{color:#080808;border-color:#ff0039}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#3fb618}.control-group.success input,.control-group.success select,.control-group.success textarea{color:#080808;border-color:#3fb618}legend{color:#080808;border-bottom:0}.form-actions{background-color:#eee;border-top:0}.dropdown-menu{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert-heading,.alert h1,.alert h2,.alert h3,.alert h4,.alert h5,.alert h6{color:#fff}.label{min-width:80px;min-height:80px;font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.label-success{background-color:#3fb618}.label-important{background-color:#ff0039}.label-info{background-color:#9954bb}.label-inverse{background-color:#000}.badge{font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.badge-success{background-color:#3fb618}.badge-important{background-color:#ff0039}.badge-info{background-color:#9954bb}.badge-inverse{background-color:#000}.hero-unit{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.well{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}[class^="icon-"],[class*=" icon-"]{margin:0 2px;vertical-align:-2px}a.thumbnail{background-color:#dfdfdf}a.thumbnail:hover{background-color:#bbb;border-color:transparent}.progress{height:6px;background-color:#eee;background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.progress .bar{background-color:#007fff;background-image:none}.progress-info{background-color:#9954bb}.progress-success{background-color:#3fb618}.progress-warning{background-color:#ff7518}.progress-danger{background-color:#ff0039}.modal{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.modal-header{border-bottom:0}.modal-footer{background-color:transparent;border-top:0}.popover{color:#fff;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.popover-title{color:#fff;border-bottom:0}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less index f753df6be8..68b3086072 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less @@ -11,7 +11,6 @@ .border-radius(@baseBorderRadius); > li { display: inline-block; - .ie7-inline-block(); text-shadow: 0 1px 0 @white; > .divider { padding: 0 5px; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less index 55cdc60338..1f0c640b30 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less @@ -7,11 +7,9 @@ .btn-group { position: relative; display: inline-block; - .ie7-inline-block(); font-size: 0; // remove as part 1 of font-size inline-block hack vertical-align: middle; // match .btn alignment given font-size hack above white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page) - .ie7-restore-left-whitespace(); } // Space out series of button groups @@ -203,7 +201,6 @@ .btn-group-vertical { display: inline-block; // makes buttons only take up the width they need - .ie7-inline-block(); } .btn-group-vertical > .btn { display: block; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less index 4cd4d862b3..e522ab536a 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less @@ -9,7 +9,6 @@ // Core .btn { display: inline-block; - .ie7-inline-block(); padding: 4px 12px; margin-bottom: 0; // For input.btn font-size: @baseFontSize; @@ -19,10 +18,8 @@ cursor: pointer; .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark, 0 1px 1px rgba(255,255,255,.75)); border: 1px solid @btnBorder; - *border: 0; // Remove the border to prevent IE7's black border on input:focus border-bottom-color: darken(@btnBorder, 10%); .border-radius(@baseBorderRadius); - .ie7-restore-left-whitespace(); // Give IE7 some love .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); // Hover/focus state @@ -178,23 +175,6 @@ input[type="submit"].btn { padding: 0; border: 0; } - - // IE7 has some default padding on button controls - *padding-top: 3px; - *padding-bottom: 3px; - - &.btn-large { - *padding-top: 7px; - *padding-bottom: 7px; - } - &.btn-small { - *padding-top: 3px; - *padding-bottom: 3px; - } - &.btn-mini { - *padding-top: 1px; - *padding-bottom: 1px; - } } diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less index 685523eb0a..0b90a13059 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less @@ -34,7 +34,6 @@ pre.code { white-space: pre; white-space: pre-wrap; background-color: #f5f5f5; - border: 1px solid #ccc; // fallback for IE7-8 border: 1px solid rgba(0,0,0,.15); .border-radius(@baseBorderRadius); @@ -58,4 +57,4 @@ pre.code { .pre-scrollable { max-height: 340px; overflow-y: scroll; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less index 35730a6bba..7707e04feb 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less @@ -8,10 +8,7 @@ .dropdown { position: relative; } -.dropdown-toggle { - // The caret makes the toggle a bit too tall in IE7 - *margin-bottom: -3px; -} + .dropdown-toggle:active, .open .dropdown-toggle { outline: 0; @@ -50,15 +47,10 @@ margin: 2px 0 0; // override default ul list-style: none; background-color: @dropdownBackground; - border: 1px solid #ccc; // Fallback for IE7-8 border: 1px solid @dropdownBorder; - *border-right-width: 2px; - *border-bottom-width: 2px; .border-radius(6px); .box-shadow(0 5px 10px rgba(0,0,0,.2)); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; + background-clip: padding-box; // Aligns the dropdown menu to right &.pull-right { @@ -120,17 +112,12 @@ text-decoration: none; background-color: transparent; background-image: none; // Remove CSS gradient - .reset-filter(); cursor: default; } // Open state for the dropdown // --------------------------- .open { - // IE7's z-index only goes to the nearest positioned ancestor, which would - // make the menu appear below buttons that appeared later on the page - *z-index: @zindexDropdown; - & > .dropdown-menu { display: block; } diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less index 6bbbacd1e3..38ed7a9914 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less @@ -135,8 +135,6 @@ input[type="color"], input[type="radio"], input[type="checkbox"] { margin: 4px 0 0; - *margin-top: 0; /* IE7 */ - margin-top: 1px \9; /* IE8-9 */ line-height: normal; } @@ -154,8 +152,7 @@ input[type="checkbox"] { // Set the height of select and file controls to match text inputs select, input[type="file"] { - height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ - *margin-top: 4px; /* For IE7, add top margin to align select with labels */ + height: @inputHeight; line-height: @inputHeight; } @@ -409,7 +406,6 @@ select:focus:invalid { .help-inline { display: inline-block; - .ie7-inline-block(); vertical-align: middle; padding-left: 5px; } @@ -542,9 +538,7 @@ select:focus:invalid { input.search-query { padding-right: 14px; - padding-right: 4px \9; padding-left: 14px; - padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ margin-bottom: 0; // Remove the default margin on all inputs .border-radius(15px); } @@ -587,7 +581,6 @@ input.search-query { .input-prepend, .input-append { display: inline-block; - .ie7-inline-block(); margin-bottom: 0; vertical-align: middle; } @@ -658,15 +651,7 @@ legend + .control-group { } // Move over all input controls and content .controls { - // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend - // don't inherit the margin of the parent, in this case .controls - *display: inline-block; - *padding-left: 20px; margin-left: @horizontalComponentOffset; - *margin-left: 0; - &:first-child { - *padding-left: @horizontalComponentOffset; - } } // Remove bottom margin on block level help text since that's accounted for on .control-group .help-block { diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less index 79d889219f..ec4f1abed9 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less @@ -42,31 +42,6 @@ margin-right: auto; } -// IE7 inline-block -// ---------------- -.ie7-inline-block() { - *display: inline; /* IE7 inline-block hack */ - *zoom: 1; -} - -// IE7 likes to collapse whitespace on either side of the inline-block elements. -// Ems because we're attempting to match the width of a space character. Left -// version is for form buttons, which typically come after other elements, and -// right version is for icons, which come before. Applying both is ok, but it will -// mean that space between those elements will be .6em (~2 space characters) in IE7, -// instead of the 1 space in other browsers. -.ie7-restore-left-whitespace() { - *margin-left: .3em; - - &:first-child { - *margin-left: 0; - } -} - -.ie7-restore-right-whitespace() { - *margin-right: .3em; -} - // Sizing shortcuts // ------------------------- .size(@height, @width) { @@ -203,31 +178,21 @@ // Border Radius .border-radius(@radius) { - -webkit-border-radius: @radius; - -moz-border-radius: @radius; - border-radius: @radius; + border-radius: @radius; } // Single Corner Border Radius .border-top-left-radius(@radius) { - -webkit-border-top-left-radius: @radius; - -moz-border-radius-topleft: @radius; - border-top-left-radius: @radius; + border-top-left-radius: @radius; } .border-top-right-radius(@radius) { - -webkit-border-top-right-radius: @radius; - -moz-border-radius-topright: @radius; - border-top-right-radius: @radius; + border-top-right-radius: @radius; } .border-bottom-right-radius(@radius) { - -webkit-border-bottom-right-radius: @radius; - -moz-border-radius-bottomright: @radius; - border-bottom-right-radius: @radius; + border-bottom-right-radius: @radius; } .border-bottom-left-radius(@radius) { - -webkit-border-bottom-left-radius: @radius; - -moz-border-radius-bottomleft: @radius; - border-bottom-left-radius: @radius; + border-bottom-left-radius: @radius; } // Single Side Border Radius @@ -250,66 +215,36 @@ // Drop shadows .box-shadow(@shadow) { - -webkit-box-shadow: @shadow; - -moz-box-shadow: @shadow; - box-shadow: @shadow; + box-shadow: @shadow; } // Transitions .transition(@transition) { - -webkit-transition: @transition; - -moz-transition: @transition; - -o-transition: @transition; - transition: @transition; + transition: @transition; } .transition-delay(@transition-delay) { - -webkit-transition-delay: @transition-delay; - -moz-transition-delay: @transition-delay; - -o-transition-delay: @transition-delay; - transition-delay: @transition-delay; + transition-delay: @transition-delay; } .transition-duration(@transition-duration) { - -webkit-transition-duration: @transition-duration; - -moz-transition-duration: @transition-duration; - -o-transition-duration: @transition-duration; - transition-duration: @transition-duration; + transition-duration: @transition-duration; } // Transformations .rotate(@degrees) { - -webkit-transform: rotate(@degrees); - -moz-transform: rotate(@degrees); - -ms-transform: rotate(@degrees); - -o-transform: rotate(@degrees); - transform: rotate(@degrees); + transform: rotate(@degrees); } .scale(@ratio) { - -webkit-transform: scale(@ratio); - -moz-transform: scale(@ratio); - -ms-transform: scale(@ratio); - -o-transform: scale(@ratio); - transform: scale(@ratio); + transform: scale(@ratio); } .translate(@x, @y) { - -webkit-transform: translate(@x, @y); - -moz-transform: translate(@x, @y); - -ms-transform: translate(@x, @y); - -o-transform: translate(@x, @y); - transform: translate(@x, @y); + transform: translate(@x, @y); } .skew(@x, @y) { - -webkit-transform: skew(@x, @y); - -moz-transform: skew(@x, @y); - -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 - -o-transform: skew(@x, @y); - transform: skew(@x, @y); + transform: skew(@x, @y); -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 } .translate3d(@x, @y, @z) { - -webkit-transform: translate3d(@x, @y, @z); - -moz-transform: translate3d(@x, @y, @z); - -o-transform: translate3d(@x, @y, @z); - transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); } // Backface visibility @@ -325,25 +260,18 @@ // Background clipping // Heads up: FF 3.6 and under need "padding" instead of "padding-box" .background-clip(@clip) { - -webkit-background-clip: @clip; - -moz-background-clip: @clip; - background-clip: @clip; + background-clip: @clip; } // Background sizing .background-size(@size) { - -webkit-background-size: @size; - -moz-background-size: @size; - -o-background-size: @size; - background-size: @size; + background-size: @size; } // Box sizing .box-sizing(@boxmodel) { - -webkit-box-sizing: @boxmodel; - -moz-box-sizing: @boxmodel; - box-sizing: @boxmodel; + box-sizing: @boxmodel; } // User select @@ -385,7 +313,6 @@ // Opacity .opacity(@opacity) { opacity: @opacity / 100; - filter: ~"alpha(opacity=@{opacity})"; } @@ -423,7 +350,6 @@ background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down } .vertical(@startColor: #555, @endColor: #333) { background-color: mix(@startColor, @endColor, 60%); @@ -433,7 +359,6 @@ background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down } .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { background-color: @endColor; @@ -451,7 +376,6 @@ background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback } .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { @@ -462,7 +386,6 @@ background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback } .radial(@innerColor: #555, @outerColor: #333) { background-color: @outerColor; @@ -481,10 +404,6 @@ background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); } } -// Reset filters for IE -.reset-filter() { - filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); -} @@ -495,14 +414,8 @@ // ------------------------- // Dividers (basically an hr) within dropdowns and nav lists .nav-divider(@top: #e5e5e5, @bottom: @white) { - // IE7 needs a set width since we gave a height. Restricting just - // to IE7 to keep the 1px left/right space in other browsers. - // It is unclear where IE is getting the extra space that we need - // to negative-margin away, but so it goes. - *width: 100%; height: 1px; margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px - *margin: -5px 0 5px; overflow: hidden; background-color: @top; border-bottom: 1px solid @bottom; @@ -513,17 +426,13 @@ .buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { // gradientBar will set the background to a pleasing blend of these, to support IE<=9 .gradientBar(@startColor, @endColor, @textColor, @textShadow); - *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - .reset-filter(); // in these cases the gradient won't cover the background, so we override &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { color: @textColor; background-color: @endColor; - *background-color: darken(@endColor, 5%); } - // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves &:active, &.active { background-color: darken(@endColor, 10%) e("\9"); diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less index 540e7973bb..6d3d06f36a 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less @@ -10,10 +10,6 @@ .navbar { overflow: visible; margin-bottom: @baseLineHeight; - - // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar - *position: relative; - *z-index: 2; } // Inner for background effects @@ -475,9 +471,7 @@ width: (@gridColumnWidth * @gridColumns) + (@gridGutterWidth * (@gridColumns - 1 .transition(none); .placeholder(@navbarInverseSearchPlaceholderColor); - // Focus states (we use .focused since IE7-8 and down doesn't support :focus) - &:focus, - &.focused { + &:focus{ padding: 5px 15px; color: @grayDark; text-shadow: 0 1px 0 @white; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less index a789db2d28..6f6e6ac7de 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less @@ -10,7 +10,6 @@ .pagination ul { // Allow for text-based alignment display: inline-block; - .ie7-inline-block(); // Reset default ul styles margin-left: 0; margin-bottom: 0; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less index aae35c8cd5..0836e896d2 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less @@ -13,9 +13,7 @@ padding: 1px; text-align: left; // Reset given new insertion method background-color: @popoverBackground; - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; + background-clip: padding-box; border: 1px solid #ccc; border: 1px solid rgba(0,0,0,.2); .border-radius(6px); diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less index 5e0c3dda01..d8822ee848 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less @@ -6,30 +6,6 @@ // ANIMATIONS // ---------- -// Webkit -@-webkit-keyframes progress-bar-stripes { - from { background-position: 40px 0; } - to { background-position: 0 0; } -} - -// Firefox -@-moz-keyframes progress-bar-stripes { - from { background-position: 40px 0; } - to { background-position: 0 0; } -} - -// IE9 -@-ms-keyframes progress-bar-stripes { - from { background-position: 40px 0; } - to { background-position: 0 0; } -} - -// Opera -@-o-keyframes progress-bar-stripes { - from { background-position: 0 0; } - to { background-position: 40px 0; } -} - // Spec @keyframes progress-bar-stripes { from { background-position: 40px 0; } @@ -77,11 +53,7 @@ // Call animation for the active one .progress.active .bar { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -moz-animation: progress-bar-stripes 2s linear infinite; - -ms-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; } diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less index 4806bd5e59..01be666671 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less @@ -79,7 +79,6 @@ sub { img { /* Responsive images (ensure images don't scale beyond their parents) */ max-width: 100%; /* Part 1: Set a maxium relative to the parent */ - width: auto\9; /* IE7-8 need help adjusting responsive images */ height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ vertical-align: middle; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less index 128f4ce30d..0829546ba2 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less @@ -122,7 +122,7 @@ // Smooth out the collapsing/expanding nav .nav-collapse { - -webkit-transform: translate3d(0, 0, 0); // activate the GPU + transform: translate3d(0, 0, 0); // activate the GPU } // Block level the page header small tag for readability diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less index 1812bf71ac..d73e23f5ea 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less @@ -19,7 +19,6 @@ display: inline-block; width: 14px; height: 14px; - .ie7-restore-right-whitespace(); line-height: 14px; vertical-align: text-top; background-image: url("@{iconSpritePath}"); diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less index 0e35271e11..f88b66bd18 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less @@ -74,7 +74,6 @@ table { .table-bordered { border: 1px solid @tableBorder; border-collapse: separate; // Done so we can round those corners! - *border-collapse: collapse; // IE7 can't round corners anyway border-left: 0; .border-radius(@baseBorderRadius); th, diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css index 0f5604ee68..98ba23f816 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css @@ -73,9 +73,7 @@ body { width: 100%; height: 400px; margin: 20px 0; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; + border-radius: 5px; } .gradient-horizontal { @@ -86,7 +84,6 @@ body { background-image: -o-linear-gradient(left, #555555, #333333); background-image: linear-gradient(to right, #555555, #333333); background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=1); } .gradient-vertical { @@ -97,7 +94,6 @@ body { background-image: -o-linear-gradient(top, #555555, #333333); background-image: linear-gradient(to bottom, #555555, #333333); background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=0); } .gradient-directional { @@ -117,7 +113,6 @@ body { background-image: -o-linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); background-image: linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); background-repeat: no-repeat; - filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff00b3ee', endColorstr='#ffc3325f', GradientType=0); } .gradient-radial { @@ -146,5 +141,4 @@ body { background-image: -o-linear-gradient(left, #00b3ee, #7a43b6 50%, #c3325f); background-image: linear-gradient(to right, #00b3ee, #7a43b6 50%, #c3325f); background-repeat: no-repeat; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00b3ee', endColorstr='#c3325f', GradientType=0); } diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less index 337138ac8e..bf1167f950 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less @@ -130,7 +130,6 @@ ol.inline { list-style: none; > li { display: inline-block; - .ie7-inline-block(); padding-left: 5px; padding-right: 5px; } diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css index 5594f70634..84b867d2d6 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css @@ -2,27 +2,21 @@ width: 100%; } -.wmd-input { +.wmd-input { height: 300px; width: 100%; box-sizing: border-box; - -webkit-box-sizing:border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; } -.wmd-preview { +.wmd-preview { .well; width: 100%; box-sizing: border-box; - -webkit-box-sizing:border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; } .wmd-panel .btn-toolbar { margin-bottom: 0; - padding: 0; + padding: 0; width: 100%; } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index ed28a93caf..4a8c69a092 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -5210,12 +5210,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5230,17 +5232,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5357,7 +5362,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5369,6 +5375,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5383,6 +5390,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5390,12 +5398,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5414,6 +5424,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5494,7 +5505,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5506,6 +5518,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5627,6 +5640,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8510,6 +8524,16 @@ "jasmine-core": "^3.3" } }, + "karma-junit-reporter": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz", + "integrity": "sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y=", + "dev": true, + "requires": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "8.2.2" + } + }, "karma-phantomjs-launcher": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz", @@ -15402,9 +15426,9 @@ } }, "tinymce": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.0.tgz", - "integrity": "sha512-hrPeCLXY/sVCo3i64CTW8P5xbDiEI8Uii/vWpcmQWAMhex6GWWd2U+L8WIMj5tKKGdfcIQAJfpfQthc/92bcKw==" + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.2.tgz", + "integrity": "sha512-ZRoTGG4GAsOI73QPSNkabO7nkoYw9H6cglRB44W2mMkxSiqxYi8WJlgkUphk0fDqo6ZD6r3E+NSP4UHxF2lySg==" }, "tmp": { "version": "0.0.33", @@ -16226,6 +16250,12 @@ "ultron": "~1.1.0" } }, + "xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", + "dev": true + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 3f4314db0e..3a5ff22f04 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -39,7 +39,7 @@ "npm": "^6.4.1", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", - "tinymce": "4.9.0", + "tinymce": "4.9.2", "typeahead.js": "0.11.1", "underscore": "1.9.1" }, @@ -48,6 +48,7 @@ "@babel/preset-env": "7.1.6", "autoprefixer": "9.3.1", "cssnano": "4.1.7", + "fs": "0.0.2", "gulp": "^3.9.1", "gulp-babel": "8.0.0", "gulp-clean-css": "4.0.0", @@ -58,6 +59,7 @@ "gulp-imagemin": "^4.1.0", "gulp-less": "4.0.1", "gulp-ngdocs": "0.3.0", + "gulp-notify": "^3.0.0", "gulp-open": "3.0.1", "gulp-postcss": "8.0.0", "gulp-rename": "1.4.0", @@ -65,16 +67,15 @@ "gulp-watch": "5.0.1", "gulp-wrap": "0.14.0", "gulp-wrap-js": "0.4.1", - "gulp-notify": "^3.0.0", "jasmine-core": "3.3.0", "karma": "3.1.1", "karma-jasmine": "2.0.1", + "karma-junit-reporter": "^1.2.0", "karma-phantomjs-launcher": "1.0.4", "less": "3.9.0", "lodash": "4.17.11", "marked": "^0.5.2", "merge-stream": "1.0.1", - "run-sequence": "2.2.1", - "fs": "0.0.2" + "run-sequence": "2.2.1" } } diff --git a/src/Umbraco.Web.UI.Client/setupgulp.bat b/src/Umbraco.Web.UI.Client/setupgulp.bat deleted file mode 100644 index 6a53e11b71..0000000000 --- a/src/Umbraco.Web.UI.Client/setupgulp.bat +++ /dev/null @@ -1,22 +0,0 @@ -@ECHO OFF - -ECHO. -ECHO. -ECHO This will only work when you have NPM available on the command line -ECHO Works great with NodeJS Portable - https://gareth.flowers/nodejs-portable/ -ECHO. -ECHO. -set /P c=Are you sure you want to continue [Y/N]? -if /I "%c%" EQU "Y" goto :setupgulp -if /I "%c%" EQU "N" goto :eof - -:setupgulp -call npm install -call npm -g install gulp -call npm -g install gulp-cli - -ECHO. -ECHO. -ECHO You should now be able to run: gulp build or gulp dev -ECHO. -ECHO. \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.eot deleted file mode 100644 index d41103b2a9..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.ttf deleted file mode 100644 index 45c55e4e2e..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.eot deleted file mode 100644 index 74ea6229ba..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.ttf deleted file mode 100644 index 76563c22f8..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.eot deleted file mode 100644 index d90b47b81e..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.ttf deleted file mode 100644 index c598c24a3d..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.eot deleted file mode 100644 index 17216efe59..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.ttf deleted file mode 100644 index c1f225af19..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.eot deleted file mode 100644 index 403b9a5bd3..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.ttf deleted file mode 100644 index c61fc07cab..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.eot deleted file mode 100644 index 865537d94f..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.ttf deleted file mode 100644 index 6af1b85dfd..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.eot deleted file mode 100644 index 52ee50c857..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.ttf deleted file mode 100644 index b8810365ec..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.eot deleted file mode 100644 index 96a9035981..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.ttf deleted file mode 100644 index bcc57780d8..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.eot deleted file mode 100644 index 6bbc3cf58c..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.svg b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.svg deleted file mode 100644 index 25a3952340..0000000000 --- a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.svg +++ /dev/null @@ -1,1831 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.ttf deleted file mode 100644 index c537f8382a..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.woff deleted file mode 100644 index e231183dce..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular-webfont.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular.ttf deleted file mode 100644 index db433349b7..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Regular.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.eot b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.eot deleted file mode 100644 index d8375dd0ab..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.svg b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.svg deleted file mode 100644 index eec4db8bd7..0000000000 --- a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.ttf b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.ttf deleted file mode 100644 index b3290843a7..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.woff deleted file mode 100644 index 28d6adee03..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/opensans/OpenSans-Semibold-webfont.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js deleted file mode 100644 index 5476404e6a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js +++ /dev/null @@ -1,19 +0,0 @@ - -LazyLoad.js([ - '../lib/jquery/jquery.min.js', - '../lib/angular/angular.js', - '../lib/underscore/underscore-min.js', - '../lib/umbraco/Extensions.js', - '../js/app.js', - '../js/umbraco.resources.js', - '../js/umbraco.services.js', - '../js/umbraco.interceptors.js', - '../ServerVariables', - '../lib/signalr/jquery.signalR.js', - '../BackOffice/signalr/hubs', - '../js/umbraco.canvasdesigner.js' -], function () { - jQuery(document).ready(function () { - angular.bootstrap(document, ['Umbraco.canvasdesigner']); - }); -}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/sectionicon.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/sectionicon.directive.js deleted file mode 100644 index 28b6620aeb..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/sectionicon.directive.js +++ /dev/null @@ -1,30 +0,0 @@ -angular.module("umbraco.directives") -.directive('sectionIcon', function ($compile, iconHelper) { - return { - restrict: 'E', - replace: true, - - link: function (scope, element, attrs) { - - var icon = attrs.icon; - - if (iconHelper.isLegacyIcon(icon)) { - //its a known legacy icon, convert to a new one - element.html(""); - } - else if (iconHelper.isFileBasedIcon(icon)) { - var convert = iconHelper.convertFromLegacyImage(icon); - if(convert){ - element.html(""); - }else{ - element.html(""); - } - //it's a file, normally legacy so look in the icon tray images - } - else { - //it's normal - element.html(""); - } - } - }; -}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index 891e8f0e7b..39058b32d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -48,6 +48,7 @@ vm.externalLoginInfo = externalLoginInfo; vm.resetPasswordCodeInfo = resetPasswordCodeInfo; vm.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; + vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail; vm.$onInit = onInit; vm.togglePassword = togglePassword; @@ -61,6 +62,15 @@ vm.requestPasswordResetSubmit = requestPasswordResetSubmit; vm.setPasswordSubmit = setPasswordSubmit; + vm.labels = {}; + localizationService.localizeMany([ + vm.usernameIsEmail ? "general_email" : "general_username", + vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint"] + ).then(function (data) { + vm.labels.usernameLabel = data[0]; + vm.labels.usernamePlaceholder = data[1]; + }) + function onInit() { // Check if it is a new user diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 5006087ca5..14c9878839 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -105,11 +105,6 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se return; } - if (scope.userDialog) { - closeUserDialog(); - } - - navigationService.hideSearch(); navigationService.showTree(section.alias); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 40a06af87e..e5b0dbf201 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -3,8 +3,8 @@ function ContentEditController($rootScope, $scope, $routeParams, $q, $window, appState, contentResource, entityResource, navigationService, notificationsService, - serverValidationManager, contentEditingHelper, treeService, formHelper, umbRequestHelper, - editorState, $http, eventsService, relationResource, overlayService) { + serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper, + editorState, $http, eventsService, overlayService, $location) { var evts = []; var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode; @@ -22,7 +22,7 @@ $scope.page.isNew = $scope.isNew ? true : false; $scope.page.buttonGroupState = "init"; $scope.page.hideActionsMenu = infiniteMode ? true : false; - $scope.page.hideChangeVariant = infiniteMode ? true : false; + $scope.page.hideChangeVariant = false; $scope.allowOpen = true; $scope.app = null; @@ -174,8 +174,8 @@ */ function createButtons(content) { - // for trashed items, the save button is the primary action - otherwise it's a secondary action - $scope.page.saveButtonStyle = content.trashed ? "primary" : "info"; + // for trashed and element type items, the save button is the primary action - otherwise it's a secondary action + $scope.page.saveButtonStyle = content.trashed || content.isElement ? "primary" : "info"; // only create the save/publish/preview buttons if the // content app is "Conent" @@ -213,24 +213,7 @@ $scope.page.showPreviewButton = true; } - - // create infinite editing buttons - function createInfiniteModeButtons(content) { - - $scope.page.allowInfinitePublishAndClose = false; - $scope.page.allowInfiniteSaveAndClose = false; - - // check for publish rights - if (_.contains(content.allowedActions, "U")) { - $scope.page.allowInfinitePublishAndClose = true; - - // check for save rights - } else if (_.contains(content.allowedActions, "A")) { - $scope.page.allowInfiniteSaveAndClose = true; - } - - } - + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { @@ -427,6 +410,20 @@ } } + /** Just shows a simple notification that there are client side validation issues to be fixed */ + function showValidationNotification() { + //TODO: We need to make the validation UI much better, there's a lot of inconsistencies in v8 including colors, issues with the property groups and validation errors between variants + + //need to show a notification else it's not clear there was an error. + localizationService.localizeMany([ + "speechBubbles_validationFailedHeader", + "speechBubbles_validationFailedMessage" + ] + ).then(function (data) { + notificationsService.error(data[0], data[1]); + }); + } + if ($scope.page.isNew) { $scope.page.loading = true; @@ -508,7 +505,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveToPublish", - submit: function (model) { + submit: function(model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -516,28 +513,32 @@ saveMethod: contentResource.sendToPublish, action: "sendToPublish", showNotifications: false - }).then(function (data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, function (err) { - clearDirtyState($scope.content.variants); - model.submitButtonState = "error"; - //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); - }); + }).then(function(data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function(err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); }, - close: function () { + close: function() { overlayService.close(); } }; overlayService.open(dialog); } + else { + showValidationNotification(); + } } else { $scope.page.buttonGroupState = "busy"; @@ -564,7 +565,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveAndPublish", - submit: function (model) { + submit: function(model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -572,14 +573,14 @@ saveMethod: contentResource.publish, action: "publish", showNotifications: false - }).then(function (data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function (err) { + }).then(function(data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function(err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -588,13 +589,16 @@ return $q.when(err); }); }, - close: function () { + close: function() { overlayService.close(); } }; overlayService.open(dialog); } + else { + showValidationNotification(); + } } else { //ensure the flags are set @@ -617,7 +621,7 @@ // TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant if (isContentCultureVariant()) { //before we launch the dialog we want to execute all client side validations first - if (formHelper.submitForm({ scope: $scope, action: "save" })) { + if (formHelper.submitForm({ scope: $scope, action: "openSaveDialog" })) { var dialog = { parentScope: $scope, @@ -625,7 +629,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_save", - submit: function (model) { + submit: function(model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -633,28 +637,32 @@ saveMethod: $scope.saveMethod(), action: "save", showNotifications: false - }).then(function (data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, function (err) { - clearDirtyState($scope.content.variants); - model.submitButtonState = "error"; - //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); - }); + }).then(function(data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function(err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); }, - close: function (oldModel) { + close: function(oldModel) { overlayService.close(); } }; overlayService.open(dialog); } + else { + showValidationNotification(); + } } else { //ensure the flags are set @@ -737,6 +745,9 @@ }; overlayService.open(dialog); } + else { + showValidationNotification(); + } }; $scope.publishDescendants = function() { @@ -793,49 +804,48 @@ }; overlayService.open(dialog); } + else { + showValidationNotification(); + } }; $scope.preview = function (content) { + // Chromes popup blocker will kick in if a window is opened + // without the initial scoped request. This trick will fix that. + // + var previewWindow = $window.open('preview/?init=true', 'umbpreview'); + // Build the correct path so both /#/ and #/ work. + var query = 'id=' + content.id; + if ($scope.culture) { + query += "#?culture=" + $scope.culture; + } + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query; - if (!$scope.busy) { - - // Chromes popup blocker will kick in if a window is opened - // without the initial scoped request. This trick will fix that. - // - var previewWindow = $window.open('preview/?init=true', 'umbpreview'); - - // Build the correct path so both /#/ and #/ work. - var query = 'id=' + content.id; + //The user cannot save if they don't have access to do that, in which case we just want to preview + //and that's it otherwise they'll get an unauthorized access message + if (!_.contains(content.allowedActions, "A")) { + previewWindow.location.href = redirect; + } + else { + var selectedVariant = $scope.content.variants[0]; if ($scope.culture) { - query += "#?culture=" + $scope.culture; - } - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query; - - //The user cannot save if they don't have access to do that, in which case we just want to preview - //and that's it otherwise they'll get an unauthorized access message - if (!_.contains(content.allowedActions, "A")) { - previewWindow.location.href = redirect; - } - else { - var selectedVariant; - if (!$scope.culture) { - selectedVariant = $scope.content.variants[0]; - } - else { - selectedVariant = _.find($scope.content.variants, function (v) { - return v.language.culture === $scope.culture; - }); - } - - //ensure the save flag is set - selectedVariant.save = true; - performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) { - previewWindow.location.href = redirect; - }, function (err) { - //validation issues .... + var found = _.find($scope.content.variants, function (v) { + return (v.language && v.language.culture === $scope.culture); }); + + if (found) { + selectedVariant = found; + } } + + //ensure the save flag is set + selectedVariant.save = true; + performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) { + previewWindow.location.href = redirect; + }, function (err) { + //validation issues .... + }); } }; @@ -871,11 +881,19 @@ $scope.app = app; - if (infiniteMode) { - createInfiniteModeButtons($scope.content); - } else { - createButtons($scope.content); - } + $scope.$broadcast("editors.apps.appChanged", { app: app }); + + createButtons($scope.content); + + }; + + /** + * Call back when a content app changes + * @param {any} app + */ + $scope.appAnchorChanged = function (app, anchor) { + //send an event downwards + $scope.$broadcast("editors.apps.appAnchorChanged", { app: app, anchor: anchor }); }; // methods for infinite editing @@ -884,6 +902,18 @@ $scope.infiniteModel.close($scope.infiniteModel); } }; + + /** + * Call back when user click the back-icon + */ + $scope.onBack = function() { + if ($scope.infiniteModel && $scope.infiniteModel.close) { + $scope.infiniteModel.close($scope.infiniteModel); + } else { + // navigate backwards if content has a parent. + $location.path('/' + $routeParams.section + '/' + $routeParams.tree + '/' + $routeParams.method + '/' + $scope.content.parentId); + } + }; //ensure to unregister from all events! $scope.$on('$destroy', function () { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 06a147969a..d4b617f94b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -3,7 +3,7 @@ function ContentNodeInfoDirective($timeout, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource, overlayService) { - function link(scope, umbVariantContentCtrl) { + function link(scope) { var evts = []; var isInfoTab = false; @@ -44,7 +44,8 @@ "prompt_doctypeChangeWarning", "general_history", "auditTrails_historyIncludingVariants", - "content_itemNotPublished" + "content_itemNotPublished", + "general_choose" ]; localizationService.localizeMany(keys) @@ -59,10 +60,11 @@ labels.notPublished = data[9]; scope.historyLabel = scope.node.variants && scope.node.variants.length === 1 ? data[7] : data[8]; + scope.chooseLabel = data[10]; setNodePublishStatus(); - if (scope.currentUrls.length === 0) { + if (scope.currentUrls && scope.currentUrls.length === 0) { if (scope.node.id > 0) { //it's created but not published scope.currentUrls.push({ text: labels.notPublished, isUrl: false }); @@ -96,22 +98,19 @@ scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id; } - //load in the audit trail if we are currently looking at the INFO tab - if (umbVariantContentCtrl && umbVariantContentCtrl.editor) { - var activeApp = _.find(umbVariantContentCtrl.editor.content.apps, a => a.active); - if (activeApp && activeApp.alias === "umbInfo") { - isInfoTab = true; - loadAuditTrail(); - loadRedirectUrls(); - } + var activeApp = _.find(scope.node.apps, (a) => a.active); + if (activeApp.alias === "umbInfo") { + loadRedirectUrls(); + loadAuditTrail(); } + // never show templates for element types (if they happen to have been created in the content tree) + scope.disableTemplates = scope.disableTemplates || scope.node.isElement; } scope.auditTrailPageChange = function (pageNumber) { scope.auditTrailOptions.pageNumber = pageNumber; - auditTrailLoaded = false; - loadAuditTrail(); + loadAuditTrail(true); }; scope.openDocumentType = function (documentType) { @@ -192,10 +191,12 @@ editorService.rollback(rollback); }; - function loadAuditTrail() { + function loadAuditTrail(forceReload) { //don't load this if it's already done - if (auditTrailLoaded) { return; }; + if (auditTrailLoaded && !forceReload) { + return; + } scope.loadingAuditTrail = true; @@ -304,6 +305,12 @@ } function updateCurrentUrls() { + // never show urls for element types (if they happen to have been created in the content tree) + if (scope.node.isElement) { + scope.currentUrls = null; + return; + } + // find the urls for the currently selected language if (scope.node.variants.length > 1) { // nodes with variants @@ -334,12 +341,11 @@ if (newValue === oldValue) { return; } if (isInfoTab) { - auditTrailLoaded = false; - loadAuditTrail(); + loadAuditTrail(true); loadRedirectUrls(); setNodePublishStatus(); - updateCurrentUrls(); } + updateCurrentUrls(); }); //ensure to unregister from all events! diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js index 5ebb40fac6..06f426889f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js @@ -2,40 +2,153 @@ 'use strict'; /** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */ - function tabbedContentDirective() { + function tabbedContentDirective($timeout) { + + function link($scope, $element, $attrs) { + + var appRootNode = $element[0]; + + // Directive for cached property groups. + var propertyGroupNodesDictionary = {}; + + var scrollableNode = appRootNode.closest(".umb-scrollable"); + scrollableNode.addEventListener("scroll", onScroll); + scrollableNode.addEventListener("mousewheel", cancelScrollTween); + + function onScroll(event) { + + var viewFocusY = scrollableNode.scrollTop + scrollableNode.clientHeight * .5; + + for(var i in $scope.content.tabs) { + var group = $scope.content.tabs[i]; + var node = propertyGroupNodesDictionary[group.id]; + if (viewFocusY >= node.offsetTop && viewFocusY <= node.offsetTop + node.clientHeight) { + setActiveAnchor(group); + return; + } + } + + } + + function setActiveAnchor(tab) { + if (tab.active !== true) { + var i = $scope.content.tabs.length; + while(i--) { + $scope.content.tabs[i].active = false; + } + tab.active = true; + } + } + function getActiveAnchor() { + var i = $scope.content.tabs.length; + while(i--) { + if ($scope.content.tabs[i].active === true) + return $scope.content.tabs[i]; + } + return false; + } + function getScrollPositionFor(id) { + if (propertyGroupNodesDictionary[id]) { + return propertyGroupNodesDictionary[id].offsetTop - 20;// currently only relative to closest relatively positioned parent + } + return null; + } + function scrollTo(id) { + var y = getScrollPositionFor(id); + if (getScrollPositionFor !== null) { + + var viewportHeight = scrollableNode.clientHeight; + var from = scrollableNode.scrollTop; + var to = Math.min(y, scrollableNode.scrollHeight - viewportHeight); + + var animeObject = {_y: from}; + $scope.scrollTween = anime({ + targets: animeObject, + _y: to, + easing: 'easeOutExpo', + duration: 200 + Math.min(Math.abs(to-from)/viewportHeight*100, 400), + update: () => { + scrollableNode.scrollTo(0, animeObject._y); + } + }); + + } + } + function jumpTo(id) { + var y = getScrollPositionFor(id); + if (getScrollPositionFor !== null) { + cancelScrollTween(); + scrollableNode.scrollTo(0, y); + } + } + function cancelScrollTween() { + if($scope.scrollTween) { + $scope.scrollTween.pause(); + } + } + + $scope.registerPropertyGroup = function(element, appAnchor) { + propertyGroupNodesDictionary[appAnchor] = element; + }; + + $scope.$on("editors.apps.appChanged", function($event, $args) { + // if app changed to this app, then we want to scroll to the current anchor + if($args.app.alias === "umbContent") { + var activeAnchor = getActiveAnchor(); + $timeout(jumpTo.bind(null, [activeAnchor.id])); + } + }); + + $scope.$on("editors.apps.appAnchorChanged", function($event, $args) { + if($args.app.alias === "umbContent") { + setActiveAnchor($args.anchor); + scrollTo($args.anchor.id); + } + }); + + //ensure to unregister from all dom-events + $scope.$on('$destroy', function () { + cancelScrollTween(); + scrollableNode.removeEventListener("scroll", onScroll); + scrollableNode.removeEventListener("mousewheel", cancelScrollTween); + }); + + } + + function controller($scope, $element, $attrs) { + + + //expose the property/methods for other directives to use + this.content = $scope.content; + this.activeVariant = _.find(this.content.variants, variant => { + return variant.active; + }); + + $scope.activeVariant = this.activeVariant; + + $scope.defaultVariant = _.find(this.content.variants, variant => { + return variant.language.isDefault; + }); + + $scope.unlockInvariantValue = function(property) { + property.unlockInvariantValue = !property.unlockInvariantValue; + }; + + $scope.$watch("tabbedContentForm.$dirty", + function (newValue, oldValue) { + if (newValue === true) { + $scope.content.isDirty = true; + } + } + ); + } var directive = { restrict: 'E', replace: true, templateUrl: 'views/components/content/umb-tabbed-content.html', - controller: function ($scope) { - - //expose the property/methods for other directives to use - this.content = $scope.content; - this.activeVariant = _.find(this.content.variants, variant => { - return variant.active; - }); - - $scope.activeVariant = this.activeVariant; - - $scope.defaultVariant = _.find(this.content.variants, variant => { - return variant.language.isDefault; - }); - - $scope.unlockInvariantValue = function(property) { - property.unlockInvariantValue = !property.unlockInvariantValue; - }; - - $scope.$watch("tabbedContentForm.$dirty", - function (newValue, oldValue) { - if (newValue === true) { - $scope.content.isDirty = true; - } - }); - }, - link: function(scope) { - - }, + controller: controller, + link: link, scope: { content: "=" } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index 8545854992..effa21e581 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -16,7 +16,10 @@ onCloseSplitView: "&", onSelectVariant: "&", onOpenSplitView: "&", - onSelectApp: "&" + onSelectApp: "&", + onSelectAppAnchor: "&", + onBack: "&?", + showBack: "IE9 + everyone else + ) { + // yes, let the link open itself + return; + } + event.stopPropagation(); + event.preventDefault(); + + var path = scope.pathTo(ancestor); + $location.path(path); + navigationService.clearSearch(["cculture"]); + } + + scope.pathTo = function (ancestor) { + return "/" + scope.entityType + "/" + scope.entityType + "/edit/" + ancestor.id; + } + function onInit() { if ("onOpen" in attr) { scope.allowOnOpen = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index 0d78aab0eb..4999f7007a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function EditorContentHeader($location, $routeParams) { + function EditorContentHeader() { function link(scope, el, attr, ctrl) { @@ -11,17 +11,25 @@ if (!scope.serverValidationAliasField) { scope.serverValidationAliasField = "Alias"; } - + scope.vm = {}; scope.vm.dropdownOpen = false; scope.vm.currentVariant = ""; function onInit() { + setCurrentVariant(); + + angular.forEach(scope.content.apps, (app) => { + if (app.alias === "umbContent") { + app.anchors = scope.content.tabs; + } + }); + } function setCurrentVariant() { - angular.forEach(scope.variants, function (variant) { + angular.forEach(scope.content.variants, function (variant) { if (variant.active) { scope.vm.currentVariant = variant; } @@ -29,7 +37,9 @@ } scope.goBack = function () { - $location.path('/' + $routeParams.section + '/' + $routeParams.tree + '/' + $routeParams.method + '/' + scope.menu.currentNode.parentId); + if (scope.onBack) { + scope.onBack(); + } }; scope.selectVariant = function (event, variant) { @@ -46,6 +56,12 @@ } } + scope.selectAnchorItem = function(item, anchor) { + if(scope.onSelectAnchorItem) { + scope.onSelectAnchorItem({"item": item, "anchor": anchor}); + } + } + scope.closeSplitView = function () { if (scope.onCloseSplitView) { scope.onCloseSplitView(); @@ -72,10 +88,10 @@ onInit(); //watch for the active culture changing, if it changes, update the current variant - if (scope.variants) { + if (scope.content.variants) { scope.$watch(function () { - for (var i = 0; i < scope.variants.length; i++) { - var v = scope.variants[i]; + for (var i = 0; i < scope.content.variants.length; i++) { + var v = scope.content.variants[i]; if (v.active) { return v.language.culture; } @@ -99,13 +115,14 @@ name: "=", nameDisabled: " { + if (firstRun || newVal !== undefined && newVal !== oldVal) { + firstRun = false; + scope.showNavigation = newVal > 1; + calculateVisibleItems($window.innerWidth); + } + }); } function calculateVisibleItems(windowWidth) { @@ -128,7 +135,8 @@ templateUrl: 'views/components/editor/umb-editor-navigation.html', scope: { navigation: "=", - onSelect: "&" + onSelect: "&", + onAnchorSelect: "&" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js new file mode 100644 index 0000000000..ae725ef25e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js @@ -0,0 +1,49 @@ +(function () { + 'use strict'; + + function UmbEditorNavigationItemController($scope, $element, $attrs) { + + var vm = this; + + vm.clicked = function() { + vm.onOpen({item:vm.item}); + }; + + vm.anchorClicked = function(anchor, $event) { + vm.onOpenAnchor({item:vm.item, anchor:anchor}); + $event.stopPropagation(); + $event.preventDefault(); + }; + + // needed to make sure that we update what anchors are active. + vm.mouseOver = function() { + $scope.$digest(); + } + + var componentNode = $element[0]; + + componentNode.classList.add('umb-sub-views-nav-item'); + componentNode.addEventListener('mouseover', vm.mouseOver); + + //ensure to unregister from all dom-events + $scope.$on('$destroy', function () { + componentNode.removeEventListener("mouseover", vm.mouseOver); + }); + + } + + angular + .module('umbraco.directives.html') + .component('umbEditorNavigationItem', { + templateUrl: 'views/components/editor/umb-editor-navigation-item.html', + controller: UmbEditorNavigationItemController, + controllerAs: 'vm', + bindings: { + item: '=', + onOpen: '&', + onOpenAnchor: '&', + hotkey: '<' + } + }); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js index 0db239c56a..4104a663d3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js @@ -7,168 +7,85 @@ var evts = []; var allowedNumberOfVisibleEditors = 3; - var editorIndent = 60; - + scope.editors = []; - + function addEditor(editor) { - editor.animating = true; - - showOverlayOnPrevEditor(); + editor.inFront = true; + editor.moveRight = true; + editor.level = 0; + editor.styleIndex = 0; - // start collapsing editors to make room for new ones - $timeout(function() { - - var editorsElement = el[0]; - // only select the editors which are allowed to be - // shown so we don't animate a lot of editors which aren't necessary - var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ allowedNumberOfVisibleEditors +')'); - - // collapse open editors before opening the new one - var collapseEditorAnimation = anime({ - targets: moveEditors, - width: function(el, index, length) { - // we have to resize all small editors when they move to the - // left side so they don't leave a gap - if(el.classList.contains("umb-editor--small")) { - return "100%"; - } - }, - left: function(el, index, length){ - if(length >= allowedNumberOfVisibleEditors) { - return index * editorIndent; - } - return (index + 1) * editorIndent; - }, - easing: 'easeInOutQuint', - duration: 300 - }); - - // push the new editor to the dom - scope.editors.push(editor); - - }); - - // slide the new editor in - $timeout(function() { - - var editorsElement = el[0]; - // select the last editor we just pushed - var lastEditor = editorsElement.querySelector('.umb-editor:last-of-type'); - var indentValue = scope.editors.length * editorIndent; - - /* don't allow indent larger than what - fits the max number of visible editors */ - if(scope.editors.length >= allowedNumberOfVisibleEditors) { - indentValue = allowedNumberOfVisibleEditors * editorIndent; - } - - // indent all large editors - if(editor.size !== "small") { - lastEditor.style.left = indentValue + "px"; - } - - // animation config - var addEditorAnimation = anime({ - targets: lastEditor, - translateX: [100 + '%', 0], - opacity: [0, 1], - easing: 'easeInOutQuint', - duration: 300, - complete: function() { - $timeout(function(){ - editor.animating = false; - }); - } - }); - - }); + editor.infinityMode = true; + + // push the new editor to the dom + scope.editors.push(editor); + + $timeout(() => { + editor.moveRight = false; + }) + + editor.animating = true; + setTimeout(revealEditorContent.bind(this, editor), 400); + + updateEditors(); } - + function removeEditor(editor) { + editor.moveRight = true; + editor.animating = true; - - $timeout(function(){ - - var editorsElement = el[0]; - var lastEditor = editorsElement.querySelector('.umb-editor:last-of-type'); - - var removeEditorAnimation = anime({ - targets: lastEditor, - translateX: [0, 100 + '%'], - opacity: [1, 0], - easing: 'easeInOutQuint', - duration: 300, - complete: function(a) { - $timeout(function(){ - scope.editors.splice(-1,1); - removeOverlayFromPrevEditor(); - }); - } - }); - - expandEditors(); - - }); - + setTimeout(removeEditorFromDOM.bind(this, editor), 400); + + updateEditors(-1); + } - - function expandEditors() { - // expand hidden editors - $timeout(function() { - - var editorsElement = el[0]; - // only select the editors which are allowed to be - // shown so we don't animate a lot of editors which aren't necessary - // as the last element hasn't been removed from the dom yet we have to select the last four and then skip the last child (as it is the one closing). - var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ allowedNumberOfVisibleEditors + 1 +'):not(:last-child)'); - var editorWidth = editorsElement.offsetWidth; - - var expandEditorAnimation = anime({ - targets: moveEditors, - left: function(el, index, length){ - // move the editor all the way to the right if the top one is a small - if(el.classList.contains("umb-editor--small")) { - // only change the size if it is the editor on top - if(index + 1 === length) { - return editorWidth - 500; - } - } else { - return (index + 1) * editorIndent; - } - }, - width: function(el, index, length) { - // set the correct size if the top editor is of type "small" - if(el.classList.contains("umb-editor--small") && index + 1 === length) { - return "500px"; - } - }, - easing: 'easeInOutQuint', - duration: 300 - }); - - }); - + + function revealEditorContent(editor) { + + editor.animating = false; + + scope.$digest(); + } - - // show backdrop on previous editor - function showOverlayOnPrevEditor() { - var numberOfEditors = scope.editors.length; - if(numberOfEditors > 0) { - scope.editors[numberOfEditors - 1].showOverlay = true; + + function removeEditorFromDOM(editor) { + + // push the new editor to the dom + var index = scope.editors.indexOf(editor); + if (index !== -1) { + scope.editors.splice(index, 1); } + + updateEditors(); + + scope.$digest(); + } - - function removeOverlayFromPrevEditor() { - var numberOfEditors = scope.editors.length; - if(numberOfEditors > 0) { - scope.editors[numberOfEditors - 1].showOverlay = false; + + /** update layer positions. With ability to offset positions, needed for when an item is moving out, then we dont want it to influence positions */ + function updateEditors(offset) { + + offset = offset || 0;// fallback value. + + var len = scope.editors.length; + var calcLen = len + offset; + var ceiling = Math.min(calcLen, allowedNumberOfVisibleEditors); + var origin = Math.max(calcLen-1, 0)-ceiling; + var i = 0; + while(i= ceiling; + i++; } - } + } + evts.push(eventsService.on("appState.editors.open", function (name, args) { addEditor(args.editor); })); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js new file mode 100644 index 0000000000..d44ca36a5a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -0,0 +1,59 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbCheckbox +@restrict E +@scope + +@description +Added in Umbraco version 7.14.0 Use this directive to render an umbraco checkbox. + +

Markup example

+
+    
+ + + + +
+
+ +@param {boolean} model Set to true or false to set the checkbox to checked or unchecked. +@param {string} value Set the value of the checkbox. +@param {string} name Set the name of the checkbox. +@param {string} text Set the text for the checkbox label. +@param {boolean} disabled Set the checkbox to be disabled. +@param {boolean} required Set the checkbox to be required. +@param {string} onChange Callback when the value of the input element changes. + +**/ + +(function () { + 'use strict'; + + function CheckboxDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/forms/umb-checkbox.html', + scope: { + model: "=", + value: "@", + name: "@", + text: "@", + disabled: "=", + required: "=", + onChange: "&" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbCheckbox', CheckboxDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js new file mode 100644 index 0000000000..351ba2fee2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -0,0 +1,57 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbRadiobutton +@restrict E +@scope + +@description +Added in Umbraco version 7.14.0 Use this directive to render an umbraco radio button. + +

Markup example

+
+    
+ + + + +
+
+ +@param {boolean} model Set to true or false to set the radiobutton to checked or unchecked. +@param {string} value Set the value of the radiobutton. +@param {string} name Set the name of the radiobutton. +@param {string} text Set the text for the radiobutton label. +@param {boolean} disabled Set the radiobutton to be disabled. +@param {boolean} required Set the radiobutton to be required. + +**/ + +(function () { + 'use strict'; + + function RadiobuttonDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/forms/umb-radiobutton.html', + scope: { + model: "=", + value: "@", + name: "@", + text: "@", + disabled: "=", + required: "=" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbRadiobutton', RadiobuttonDirective); + +})(); 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 0a8846f975..6472dd3d38 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 @@ -30,7 +30,8 @@ angular.module("umbraco.directives") promises.push(tinyMceService.getTinyMceEditorConfig({ htmlId: scope.uniqueId, stylesheets: scope.configuration ? scope.configuration.stylesheets : null, - toolbar: toolbar + toolbar: toolbar, + mode: scope.configuration.mode })); // pin toolbar to top of screen if we have focus and it scrolls off the screen diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js index 5be2ec1173..0eb0f506fc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js @@ -232,7 +232,7 @@ angular.module("umbraco.directives") var throttledResizing = _.throttle(function(){ resizeImageToScale(scope.dimensions.scale.current); calculateCropBox(); - }, 100); + }, 15); //happens when we change the scale diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js index edf54ca034..40def728ed 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js @@ -29,6 +29,8 @@ let typeahead; let tagsHound; + let initLoad = true; + vm.$onInit = onInit; vm.$onChanges = onChanges; vm.$onDestroy = onDestroy; @@ -53,7 +55,7 @@ vm.isLoading = false; //ensure that the models are formatted correctly - configureViewModel(); + configureViewModel(true); // Set the visible prompt to -1 to ensure it will not be visible vm.promptIsVisible = "-1"; @@ -139,8 +141,7 @@ if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) { configureViewModel(); - reValidate() - + reValidate(); } } } @@ -154,13 +155,19 @@ $element.find('.tags-' + vm.htmlId).typeahead('destroy'); } - function configureViewModel() { + function configureViewModel(isInitLoad) { if (vm.value) { if (angular.isString(vm.value) && vm.value.length > 0) { if (vm.config.storageType === "Json") { //json storage vm.viewModel = JSON.parse(vm.value); - updateModelValue(vm.viewModel); + + //if this is the first load, we are just re-formatting the underlying model to be consistent + //we don't want to notify the component parent of any changes, that will occur if the user actually + //changes a value. If we notify at this point it will signal a form dirty change which we don't want. + if (!isInitLoad) { + updateModelValue(vm.viewModel); + } } else { //csv storage @@ -174,8 +181,12 @@ return self.indexOf(v) === i; }); - updateModelValue(vm.viewModel); - + //if this is the first load, we are just re-formatting the underlying model to be consistent + //we don't want to notify the component parent of any changes, that will occur if the user actually + //changes a value. If we notify at this point it will signal a form dirty change which we don't want. + if (!isInitLoad) { + updateModelValue(vm.viewModel); + } } } else if (angular.isArray(vm.value)) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js index aac11bfd22..d998504d1b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js @@ -34,9 +34,18 @@ Use this directive to generate color swatches to pick from. } scope.setColor = function (color, $index, $event) { - scope.selectedColor = color; if (scope.onSelect) { - scope.onSelect(color.color, $index, $event); + // did the value change? + if (scope.selectedColor != null && scope.selectedColor.value === color.value) { + // User clicked the currently selected color + // to remove the selection, they don't want + // to select any color after all. + // Unselect the color + color = null; + } + + scope.selectedColor = color; + scope.onSelect({color: color, $index: $index, $event: $event}); $event.stopPropagation(); } }; @@ -55,7 +64,7 @@ Use this directive to generate color swatches to pick from. colors: '=?', size: '@', selectedColor: '=', - onSelect: '=', + onSelect: '&', useLabel: '=', useColorClass: '=?' }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js index 35bc25bbcc..953a28bd99 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js @@ -49,11 +49,11 @@ When this combination is hit an overview is opened with shortcuts based on the m "name": "Design", "shortcuts": [ { - "description": "Add tab", + "description": "Add group", "keys": [ {"key": "alt"}, {"key": "shift"}, - {"key": "t"} + {"key": "g"} ] } ] diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js index 87cd84ca40..58a5e1be0e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js @@ -10,7 +10,7 @@ bindings: { layouts: '<', activeLayout: '<', - onLayoutSelect: "&" + onLayoutSelect: '&' } }); 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 29335f276e..6a11b07734 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 @@ -157,7 +157,8 @@ Use this directive to generate a thumbnail grid of media items. item.isFolder = !mediaHelper.hasFilePropertyType(item); } - if (!item.isFolder) { + // if it's not a folder, get the thumbnail, extension etc. if we haven't already + if (!item.isFolder && !item.thumbnail) { // handle entity if(item.image) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index e8d8315b7a..fb5a558fd0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -119,9 +119,9 @@ Use this directive make an element sticky and follow the page when scrolling. } function calculateSize() { + var width = bar.innerWidth(); clonedBar.css({ - width: bar.outerWidth(), - height: bar.height() + width: width }); } @@ -133,7 +133,9 @@ Use this directive make an element sticky and follow the page when scrolling. clonedBar.addClass('-umb-sticky-bar'); clonedBar.css({ 'position': 'fixed', - 'z-index': 500, + // if you change this z-index value, make sure the sticky editor sub headers do not + // clash with umb-dropdown (e.g. the content actions dropdown in content list view) + 'z-index': 99, 'visibility': 'hidden' }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js index f93e41d0c7..6a8ffa7969 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js @@ -15,10 +15,12 @@ function umbFileUpload() { el.on('change', function (event) { var files = event.target.files; //emit event upward - scope.$emit("filesSelected", { files: files }); + scope.$emit("filesSelected", { files: files }); + //clear the element value - this allows us to pick the same file again and again + el.val(''); }); } }; } -angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); \ No newline at end of file +angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js new file mode 100644 index 0000000000..2a0c9c3aec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js @@ -0,0 +1,17 @@ +angular.module("umbraco.directives").directive("retriveDomElement", function () { + var directiveDefinitionObject = { + + restrict: "A", + selector: '[retriveDomElement]', + scope: { + "retriveDomElement": "&" + }, + link: { + post: function(scope, iElement, iAttrs, controller) { + scope.retriveDomElement({element:iElement, attributes: iAttrs}); + } + } + }; + + return directiveDefinitionObject; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js index 765f44a636..69a4fe35c9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js @@ -6,4 +6,7 @@ angular.module('umbraco.interceptors', []) $httpProvider.interceptors.push('securityInterceptor'); $httpProvider.interceptors.push('debugRequestInterceptor'); + $httpProvider.interceptors.push('doNotPostDollarVariablesOnPostRequestInterceptor'); + $httpProvider.interceptors.push('cultureRequestInterceptor'); + }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js new file mode 100644 index 0000000000..186f3accf0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js @@ -0,0 +1,36 @@ +(function() { + 'use strict'; + + /** + * Used to set the current client culture on all requests API requests + * @param {any} $routeParams + */ + function cultureRequestInterceptor($injector) { + return { + //dealing with requests: + 'request': function (config) { + + if (!Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) { + // no settings available, we're probably on the login screen + return config; + } + + if (!config.url.match(RegExp(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "\/backoffice\/", "i"))) { + // it's not an API request, no handling + return config; + } + + var $routeParams = $injector.get("$routeParams"); + if ($routeParams) { + // it's an API request, add the current client culture as a header value + config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + } + + return config; + } + }; + } + + angular.module('umbraco.interceptors').factory('cultureRequestInterceptor', cultureRequestInterceptor); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js new file mode 100644 index 0000000000..03373089d7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js @@ -0,0 +1,40 @@ +(function() { + 'use strict'; + + function removeProperty(obj, propertyPrefix) { + for (var property in obj) { + if (obj.hasOwnProperty(property)) { + + if (property.startsWith(propertyPrefix) && obj[property] !== undefined) { + obj[property] = undefined; + } + + if (typeof obj[property] === "object") { + removeProperty(obj[property], propertyPrefix); + } + } + } + + } + + function transform(data){ + removeProperty(data, "$"); + } + + function doNotPostDollarVariablesRequestInterceptor($q, urlHelper) { + return { + //dealing with requests: + 'request': function(config) { + if(config.method === "POST"){ + var clone = angular.copy(config); + transform(clone.data); + return clone; + } + + return config; + } + }; + } + + angular.module('umbraco.interceptors').factory('doNotPostDollarVariablesOnPostRequestInterceptor', doNotPostDollarVariablesRequestInterceptor); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index 0ff01780ae..8b93666172 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -529,13 +529,11 @@ angular.module('umbraco.mocks'). "sections_contour": "Umbraco Contour", "sections_help": "Help", "settings_defaulttemplate": "Default template", - "settings_dictionary editor egenskab": "Dictionary Key", "settings_importDocumentTypeHelp": "To import a document type, find the '.udt' file on your computer by clicking the 'Browse' button and click 'Import' (you'll be asked for confirmation on the next screen)", "settings_newtabname": "New Tab Title", "settings_nodetype": "Nodetype", "settings_objecttype": "Type", "settings_stylesheet": "Stylesheet", - "settings_stylesheet editor egenskab": "Stylesheet property", "settings_tab": "Tab", "settings_tabname": "Tab Title", "settings_tabs": "Tabs", diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index d9c505aa23..ba8334d307 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -26,9 +26,11 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve navReadyPromise.resolve(mainTreeApi); }); + + //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; - var retainedQueryStrings = ["mculture", "cculture"]; + var retainedQueryStrings = ["mculture"]; function setMode(mode) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 0ba795626a..ce4bf6077c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -232,7 +232,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s body_class: 'umb-rte', //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix - cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster + cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster, + + //this is used to style the inline macro bits, sorry hard coding this form now since we don't have a standalone + //stylesheet to load in for this with only these styles (the color is @pinkLight) + content_style: ".mce-content-body .umb-macro-holder { border: 3px dotted #f5c1bc; padding: 7px; display: block; margin: 3px; } .umb-rte .mce-content-body .umb-macro-holder.loading {background: url(assets/img/loader.gif) right no-repeat; background-size: 18px; background-position-x: 99%;}" }; if (tinyMceConfig.customConfig) { @@ -308,7 +312,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s */ defaultPrevalues: function () { var cfg = {}; - cfg.toolbar = ["code", "bold", "italic", "styleselect", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; + cfg.toolbar = ["ace", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "outdent", "indent", "link", "umbmediapicker", "umbmacro", "umbembeddialog"]; cfg.stylesheets = []; cfg.maxImageSize = 500; return cfg; @@ -423,8 +427,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s data["rel"] = img.id; data["data-id"] = img.id; } - - editor.insertContent(editor.dom.createHTML('img', data)); + + editor.selection.setContent(editor.dom.createHTML('img', data)); $timeout(function () { var imgElm = editor.dom.get('__mcenew'); @@ -458,7 +462,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s */ createInsertMacro: function (editor, callback) { - var createInsertMacroScope = this; + let self = this; + let activeMacroElement = null; //track an active macro element /** Adds custom rules for the macro plugin and custom serialization */ editor.on('preInit', function (args) { @@ -474,6 +479,16 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } }); + }); + + /** when the contents load we need to find any macros declared and load in their content */ + editor.on("SetContent", function (o) { + + //get all macro divs and load their content + $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () { + self.loadMacroContent($(this), null); + }); + }); /** @@ -501,203 +516,26 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s tooltip: 'Insert macro', onPostRender: function () { - var ctrl = this; - var isOnMacroElement = false; - + let ctrl = this; + /** - if the selection comes from a different element that is not the macro's - we need to check if the selection includes part of the macro, if so we'll force the selection - to clear to the next element since if people can select part of the macro markup they can then modify it. - */ - function handleSelectionChange() { - - if (!editor.selection.isCollapsed()) { - var endSelection = tinymce.activeEditor.selection.getEnd(); - var startSelection = tinymce.activeEditor.selection.getStart(); - //don't proceed if it's an entire element selected - if (endSelection !== startSelection) { - - //if the end selection is a macro then move the cursor - //NOTE: we don't have to handle when the selection comes from a previous parent because - // that is automatically taken care of with the normal onNodeChanged logic since the - // evt.element will be the macro once it becomes part of the selection. - var $testForMacro = $(endSelection).closest(".umb-macro-holder"); - if ($testForMacro.length > 0) { - - //it came from before so move after, if there is no after then select ourselves - var next = $testForMacro.next(); - if (next.length > 0) { - editor.selection.setCursorLocation($testForMacro.next().get(0)); - } else { - selectMacroElement($testForMacro.get(0)); - } - - } - } - } - } - - /** helper method to select the macro element */ - function selectMacroElement(macroElement) { - - // move selection to top element to ensure we can't edit this - editor.selection.select(macroElement); - - // check if the current selection *is* the element (ie bug) - var currentSelection = editor.selection.getStart(); - if (tinymce.isIE) { - if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { - while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { - currentSelection = currentSelection.parentNode; - } - editor.selection.select(currentSelection); - } - } - } - - /** - * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. - * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves - * from the event listener before changing selection, however, it seems that putting a break point in this method - * will always cause an 'infinite' loop as the caret keeps changing. - * - * TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/ + * Check if the macro is currently selected and toggle the menu button */ function onNodeChanged(evt) { //set our macro button active when on a node of class umb-macro-holder - var $macroElement = $(evt.element).closest(".umb-macro-holder"); - - handleSelectionChange(); - - //set the button active - ctrl.active($macroElement.length !== 0); - - if ($macroElement.length > 0) { - var macroElement = $macroElement.get(0); - - //remove the event listener before re-selecting - editor.off('NodeChange', onNodeChanged); - - selectMacroElement(macroElement); - - //set the flag - isOnMacroElement = true; - - //re-add the event listener - editor.on('NodeChange', onNodeChanged); - } else { - isOnMacroElement = false; - } + activeMacroElement = getRealMacroElem(evt.element); + //set the button active/inactive + ctrl.active(activeMacroElement !== null); } - /** when the contents load we need to find any macros declared and load in their content */ - editor.on("LoadContent", function (o) { - - //get all macro divs and load their content - $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () { - createInsertMacroScope.loadMacroContent($(this), null); - }); - - }); - - /** - * This prevents any other commands from executing when the current element is the macro so the content cannot be edited - * - * TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/ - */ - editor.on('BeforeExecCommand', function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - /** - * This double checks and ensures you can't paste content into the rendered macro - * - * TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/ - */ - editor.on("Paste", function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); + //NOTE: This could be another way to deal with the active/inactive state + //editor.on('ObjectSelected', function (e) {}); //set onNodeChanged event listener editor.on('NodeChange', onNodeChanged); - /** - * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so - * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request - * so the macro cannot be edited. - * - * TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/ - */ - editor.on('KeyDown', function (e) { - if (isOnMacroElement) { - var macroElement = editor.selection.getNode(); - - //get the 'real' element (either p or the real one) - macroElement = getRealMacroElem(macroElement); - - //prevent editing - e.preventDefault(); - e.stopPropagation(); - - var moveSibling = function (element, isNext) { - var $e = $(element); - var $sibling = isNext ? $e.next() : $e.prev(); - if ($sibling.length > 0) { - editor.selection.select($sibling.get(0)); - editor.selection.collapse(true); - } else { - //if we're moving previous and there is no sibling, then lets recurse and just select the next one - if (!isNext) { - moveSibling(element, true); - return; - } - - //if there is no sibling we'll generate a new p at the end and select it - editor.setContent(editor.getContent() + "

 

"); - editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); - editor.selection.collapse(true); - - } - }; - - //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) - //supported keys to remove the macro (8-backspace, 46-delete) - // TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? - if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) { - //move to next element - moveSibling(macroElement, true); - } else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) { - //move to prev element - moveSibling(macroElement, false); - } else if ($.inArray(e.keyCode, [8, 46]) !== -1) { - //delete macro element - - //move first, then delete - moveSibling(macroElement, false); - editor.dom.remove(macroElement); - } - return; - } - }); - }, /** The insert macro button click event handler */ @@ -710,11 +548,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //when we click we could have a macro already selected and in that case we'll want to edit the current parameters //so we'll need to extract them and submit them to the dialog. - var macroElement = editor.selection.getNode(); - macroElement = getRealMacroElem(macroElement); - if (macroElement) { + if (activeMacroElement) { //we have a macro selected so we'll need to parse it's alias and parameters - var contents = $(macroElement).contents(); + var contents = $(activeMacroElement).contents(); var comment = _.find(contents, function (item) { return item.nodeType === 8; }); @@ -724,7 +560,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var syntax = comment.textContent.trim(); var parsed = macroService.parseMacroSyntax(syntax); dialogData = { - macroData: parsed + macroData: parsed, + activeMacroElement: activeMacroElement //pass the active element along so we can retrieve it later }; } @@ -737,7 +574,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }); }, - insertMacroInEditor: function (editor, macroObject) { + insertMacroInEditor: function (editor, macroObject, activeMacroElement) { + + //Important note: the TinyMce plugin "noneditable" is used here so that the macro cannot be edited, + // for this to work the mceNonEditable class needs to come last and we also need to use the attribute contenteditable = false + // (even though all the docs and examples say that is not necessary) //put the macro syntax in comments, we will parse this out on the server side to be used //for persisting. @@ -746,11 +587,18 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var uniqueId = "umb-macro-" + editor.dom.uniqueId(); var macroDiv = editor.dom.create('div', { - 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId + 'class': 'umb-macro-holder ' + macroObject.macroAlias + " " + uniqueId + ' mceNonEditable', + 'contenteditable': 'false' }, macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); - editor.selection.setNode(macroDiv); + //if there's an activeMacroElement then replace it, otherwise set the contents of the selected node + if (activeMacroElement) { + activeMacroElement.replaceWith(macroDiv); //directly replaces the html node + } + else { + editor.selection.setNode(macroDiv); + } var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); @@ -1167,10 +1015,15 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s pinToolbar : function (editor) { + //we can't pin the toolbar if this doesn't exist (i.e. when in distraction free mode) + if (!editor.editorContainer) { + return; + } + var tinyMce = $(editor.editorContainer); var toolbar = tinyMce.find(".mce-toolbar"); var toolbarHeight = toolbar.height(); - var tinyMceRect = tinyMce[0].getBoundingClientRect(); + var tinyMceRect = editor.editorContainer.getBoundingClientRect(); var tinyMceTop = tinyMceRect.top; var tinyMceBottom = tinyMceRect.bottom; var tinyMceWidth = tinyMceRect.width; @@ -1288,7 +1141,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } }); - var self = this; + let self = this; //create link picker self.createLinkPicker(args.editor, function (currentTarget, anchorElement) { @@ -1346,7 +1199,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s dialogData: dialogData, submit: function (model) { var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - self.insertMacroInEditor(args.editor, macroObject); + self.insertMacroInEditor(args.editor, macroObject, dialogData.activeMacroElement); editorService.close(); }, close: function () { @@ -1369,7 +1222,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var aceEditor = { content: args.editor.getContent(), view: 'views/propertyeditors/rte/codeeditor.html', - size: 'small', submit: function (model) { args.editor.setContent(model.content); editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 4650458abc..6a15c0f553 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -350,6 +350,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS parent.children.splice(parent.children.indexOf(treeNode), 1); parent.hasChildren = parent.children.length !== 0; + + //Notify that the node has been removed + eventsService.emit("treeService.removeNode", { node: treeNode }); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 6991c5d386..bad5f4e342 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -161,6 +161,10 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe }, function (response) { + if (!response) { + return; //sometimes oddly this happens, nothing we can do + } + if (!response.status && response.message && response.stack) { //this is a JS/angular error that we should deal with return $q.reject({ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js index afcd3b27ff..1bda86c3b3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js @@ -12,14 +12,14 @@ { "value": 4, "name": "Inactive", "key": "Inactive", "color": "warning" } ]; - angular.forEach(userStates, function (userState) { - var key = "user_state" + userState.key; - localizationService.localize(key).then(function (value) { - var reg = /^\[[\S\s]*]$/g; - var result = reg.test(value); - if (result === false) { + localizationService.localizeMany(_.map(userStates, function (userState) { + return "user_state" + userState.key; + })).then(function (data) { + var reg = /^\[[\S\s]*]$/g; + _.each(data, function (value, index) { + if (!reg.test(value)) { // Only translate if key exists - userState.name = value; + userStates[index].name = value; } }); }); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 8e5c093462..c426d0d955 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -9,7 +9,7 @@ * * @param {navigationService} navigationService A reference to the navigationService */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) { +function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) { //this is used to trigger the tree to start loading once everything is ready var treeInitPromise = $q.defer(); @@ -249,6 +249,20 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar $scope.infiniteMode = args && args.editors.length > 0 ? true : false; })); + evts.push(eventsService.on("treeService.removeNode", function (e, args) { + //check to see if the current page has been removed + + var currentEditorState = editorState.getCurrent() + if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) { + //current page is loaded, so navigate to root + var section = appState.getSectionState("currentSection"); + $location.path("/" + section); + } + })); + + + + /** * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down */ diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index 765eb3952a..73107f79a4 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -1,5 +1,5 @@
-

Install Umbraco 8 Preview (Editor Track - what does that mean?)

+

Install Umbraco 8

Enter your name, email and password to install Umbraco 8 with its default settings, alternatively you can customize your installation

diff --git a/src/Umbraco.Web.UI.Client/src/less/application/animations.less b/src/Umbraco.Web.UI.Client/src/less/application/animations.less index 0f2dcb7a7e..91e2213775 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/animations.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/animations.less @@ -4,41 +4,35 @@ //Animate.css - https://daneden.github.io/animate.css/ //Licensed under the MIT license - https://opensource.org/licenses/MIT //Copyright (c) 2013 Daniel Eden -.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}@-webkit-keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);-ms-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);-ms-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);-ms-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;-ms-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes pulse{0%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);-ms-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}100%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(0.75,1.25,1);transform:scale3d(0.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes rubberBand{0%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);-ms-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(0.75,1.25,1);-ms-transform:scale3d(0.75,1.25,1);transform:scale3d(0.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);-ms-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);-ms-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);-ms-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}100%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);-ms-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);-ms-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}@keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);-ms-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);-ms-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);-ms-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);-ms-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);-ms-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}.swing{-webkit-transform-origin:top center;-ms-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes tada{0%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);-ms-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);-ms-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);-ms-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg)}100%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg)}100%{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;-ms-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);-ms-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);-ms-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);-ms-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);-ms-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);-ms-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);-ms-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);-ms-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);-ms-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);-ms-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn;-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);-ms-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);-ms-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);-ms-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);-ms-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);-ms-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);-ms-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);-ms-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);-ms-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);-ms-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);-ms-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);-ms-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);-ms-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);-ms-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);-ms-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);-ms-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);-ms-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);-ms-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);-ms-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut;-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);-ms-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);-ms-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);-ms-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);-ms-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);-ms-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);-ms-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);-ms-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);-ms-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);-ms-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);-ms-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);-ms-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);-ms-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);-ms-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);-ms-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);-ms-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);-ms-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);-ms-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);-ms-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0);-ms-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);-ms-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);-ms-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);-ms-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-360deg);transform:perspective(400px) rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-360deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-360deg);transform:perspective(400px) rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-ms-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-ms-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);-ms-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;-ms-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);-ms-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);-ms-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);-ms-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);-ms-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);-ms-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);-ms-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);-ms-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);-ms-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-animation-duration:.75s;animation-duration:.75s;-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);-ms-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY;-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}100%{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);-ms-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);-ms-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);-ms-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}100%{-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}100%{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}100%{-webkit-transform:translate3d(100%,0,0) skewX(30deg);-ms-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);-ms-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}100%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);-ms-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);-ms-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);-ms-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);-ms-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{-webkit-transform-origin:center;transform-origin:center;opacity:1}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}@keyframes rotateOut{0%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;opacity:1}100%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);-ms-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate(0,0,1,45deg);transform:rotate(0,0,1,45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate(0,0,1,45deg);-ms-transform:rotate(0,0,1,45deg);transform:rotate(0,0,1,45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);-ms-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);-ms-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}@keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);-ms-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}100%{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{-webkit-transform-origin:top left;-ms-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);-ms-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;-ms-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);-ms-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;-ms-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}100%{-webkit-transform:translate3d(0,700px,0);-ms-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);-ms-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg)}}@keyframes rollOut{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);-ms-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-ms-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-ms-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}100%{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}100%{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;-ms-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);-ms-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;-ms-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);-ms-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;-ms-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;-ms-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp} +.animated{animation-duration:1s;animation-fill-mode:both}.animated.infinite{animation-iteration-count:infinite}.animated.hinge{animation-duration:2s}.bounceIn,.bounceOut,.flipOutX,.flipOutY{animation-duration:.75s}@keyframes bounce{0%,100%,20%,53%,80%{transition-timing-function:cubic-bezier(.215,.61,.355,1);transform:translate3d(0,0,0)}40%,43%{transition-timing-function:cubic-bezier(.755,.050,.855,.060);transform:translate3d(0,-30px,0)}70%{transition-timing-function:cubic-bezier(.755,.050,.855,.060);transform:translate3d(0,-15px,0)}90%{transform:translate3d(0,-4px,0)}}.bounce{animation-name:bounce;transform-origin:center bottom}@keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}.flash{animation-name:flash}@keyframes pulse{0%,100%{transform:scale3d(1,1,1)}50%{transform:scale3d(1.05,1.05,1.05)}}.pulse{animation-name:pulse}@keyframes rubberBand{0%,100%{transform:scale3d(1,1,1)}30%{transform:scale3d(1.25,.75,1)}40%{transform:scale3d(.75,1.25,1)}50%{transform:scale3d(1.15,.85,1)}65%{transform:scale3d(.95,1.05,1)}75%{transform:scale3d(1.05,.95,1)}}.rubberBand{animation-name:rubberBand}@keyframes shake{0%,100%{transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{transform:translate3d(-10px,0,0)}20%,40%,60%,80%{transform:translate3d(10px,0,0)}}.shake{animation-name:shake}@keyframes swing{20%{transform:rotate3d(0,0,1,15deg)}40%{transform:rotate3d(0,0,1,-10deg)}60%{transform:rotate3d(0,0,1,5deg)}80%{transform:rotate3d(0,0,1,-5deg)}100%{transform:rotate3d(0,0,1,0deg)}}.swing{transform-origin:top center;animation-name:swing}@keyframes tada{0%,100%{transform:scale3d(1,1,1)}10%,20%{transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg)}40%,60%,80%{transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg)}}.tada{animation-name:tada}@keyframes wobble{0%,100%{transform:none}15%{transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg)}30%{transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg)}45%{transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg)}60%{transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg)}75%{transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg)}}.wobble{animation-name:wobble}@keyframes bounceIn{0%,100%,20%,40%,60%,80%{transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:scale3d(.3,.3,.3)}20%{transform:scale3d(1.1,1.1,1.1)}40%{transform:scale3d(.9,.9,.9)}60%{opacity:1;transform:scale3d(1.03,1.03,1.03)}80%{transform:scale3d(.97,.97,.97)}100%{opacity:1;transform:scale3d(1,1,1)}}.bounceIn{animation-name:bounceIn}@keyframes bounceInDown{0%,100%,60%,75%,90%{transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(0,-3000px,0)}60%{opacity:1;transform:translate3d(0,25px,0)}75%{transform:translate3d(0,-10px,0)}90%{transform:translate3d(0,5px,0)}100%{transform:none}}.bounceInDown{animation-name:bounceInDown}@keyframes bounceInLeft{0%,100%,60%,75%,90%{transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(-3000px,0,0)}60%{opacity:1;transform:translate3d(25px,0,0)}75%{transform:translate3d(-10px,0,0)}90%{transform:translate3d(5px,0,0)}100%{transform:none}}.bounceInLeft{animation-name:bounceInLeft}@keyframes bounceInRight{0%,100%,60%,75%,90%{transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(3000px,0,0)}60%{opacity:1;transform:translate3d(-25px,0,0)}75%{transform:translate3d(10px,0,0)}90%{transform:translate3d(-5px,0,0)}100%{transform:none}}.bounceInRight{animation-name:bounceInRight}@keyframes bounceInUp{0%,100%,60%,75%,90%{transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(0,3000px,0)}60%{opacity:1;transform:translate3d(0,-20px,0)}75%{transform:translate3d(0,10px,0)}90%{transform:translate3d(0,-5px,0)}100%{transform:translate3d(0,0,0)}}.bounceInUp{animation-name:bounceInUp}@keyframes bounceOut{20%{transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;transform:scale3d(.3,.3,.3)}}.bounceOut{animation-name:bounceOut}@keyframes bounceOutDown{20%{transform:translate3d(0,10px,0)}40%,45%{opacity:1;transform:translate3d(0,-20px,0)}100%{opacity:0;transform:translate3d(0,2000px,0)}}.bounceOutDown{animation-name:bounceOutDown}@keyframes bounceOutLeft{20%{opacity:1;transform:translate3d(20px,0,0)}100%{opacity:0;transform:translate3d(-2000px,0,0)}}.bounceOutLeft{animation-name:bounceOutLeft}@keyframes bounceOutRight{20%{opacity:1;transform:translate3d(-20px,0,0)}100%{opacity:0;transform:translate3d(2000px,0,0)}}.bounceOutRight{animation-name:bounceOutRight}@keyframes bounceOutUp{20%{transform:translate3d(0,-10px,0)}40%,45%{opacity:1;transform:translate3d(0,20px,0)}100%{opacity:0;transform:translate3d(0,-2000px,0)}}.bounceOutUp{animation-name:bounceOutUp}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.fadeIn{animation-name:fadeIn}@keyframes fadeInDown{0%{opacity:0;transform:translate3d(0,-100%,0)}100%{opacity:1;transform:none}}.fadeInDown{animation-name:fadeInDown}@keyframes fadeInDownBig{0%{opacity:0;transform:translate3d(0,-2000px,0)}100%{opacity:1;transform:none}}.fadeInDownBig{animation-name:fadeInDownBig}@keyframes fadeInLeft{0%{opacity:0;transform:translate3d(-100%,0,0)}100%{opacity:1;transform:none}}.fadeInLeft{animation-name:fadeInLeft}@keyframes fadeInLeftBig{0%{opacity:0;transform:translate3d(-2000px,0,0)}100%{opacity:1;transform:none}}.fadeInLeftBig{animation-name:fadeInLeftBig}@keyframes fadeInRight{0%{opacity:0;transform:translate3d(100%,0,0)}100%{opacity:1;transform:none}}.fadeInRight{animation-name:fadeInRight}@keyframes fadeInRightBig{0%{opacity:0;transform:translate3d(2000px,0,0)}100%{opacity:1;transform:none}}.fadeInRightBig{animation-name:fadeInRightBig}@keyframes fadeInUp{0%{opacity:0;transform:translate3d(0,100%,0)}100%{opacity:1;transform:none}}.fadeInUp{animation-name:fadeInUp}@keyframes fadeInUpBig{0%{opacity:0;transform:translate3d(0,2000px,0)}100%{opacity:1;transform:none}}.fadeInUpBig{animation-name:fadeInUpBig}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.fadeOut{animation-name:fadeOut}@keyframes fadeOutDown{0%{opacity:1}100%{opacity:0;transform:translate3d(0,100%,0)}}.fadeOutDown{animation-name:fadeOutDown}@keyframes fadeOutDownBig{0%{opacity:1}100%{opacity:0;transform:translate3d(0,2000px,0)}}.fadeOutDownBig{animation-name:fadeOutDownBig}@keyframes fadeOutLeft{0%{opacity:1}100%{opacity:0;transform:translate3d(-100%,0,0)}}.fadeOutLeft{animation-name:fadeOutLeft}@keyframes fadeOutLeftBig{0%{opacity:1}100%{opacity:0;transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{animation-name:fadeOutLeftBig}@keyframes fadeOutRight{0%{opacity:1}100%{opacity:0;transform:translate3d(100%,0,0)}}.fadeOutRight{animation-name:fadeOutRight}@keyframes fadeOutRightBig{0%{opacity:1}100%{opacity:0;transform:translate3d(2000px,0,0)}}.fadeOutRightBig{animation-name:fadeOutRightBig}@keyframes fadeOutUp{0%{opacity:1}100%{opacity:0;transform:translate3d(0,-100%,0)}}.fadeOutUp{animation-name:fadeOutUp}@keyframes fadeOutUpBig{0%{opacity:1}100%{opacity:0;transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{animation-name:fadeOutUpBig}@keyframes flip{0%{transform:perspective(400px) rotate3d(0,1,0,-360deg);animation-timing-function:ease-out}40%{transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);animation-timing-function:ease-out}50%{transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);animation-timing-function:ease-in}80%{transform:perspective(400px) scale3d(.95,.95,.95);animation-timing-function:ease-in}100%{transform:perspective(400px);animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;-ms-backface-visibility:visible;backface-visibility:visible;animation-name:flip}@keyframes flipInX{0%{transform:perspective(400px) rotate3d(1,0,0,90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1,0,0,-20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;animation-name:flipInX}.flipInY,.flipOutX{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important}@keyframes flipInY{0%{transform:perspective(400px) rotate3d(0,1,0,90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(0,1,0,-20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{transform:perspective(400px)}}.flipInY{backface-visibility:visible!important;animation-name:flipInY}@keyframes flipOutX{0%{transform:perspective(400px)}30%{transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}.flipOutX{animation-name:flipOutX;backface-visibility:visible!important}@keyframes flipOutY{0%{transform:perspective(400px)}30%{transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;animation-name:flipOutY}@keyframes lightSpeedIn{0%{transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{transform:skewX(20deg);opacity:1}80%{transform:skewX(-5deg);opacity:1}100%{transform:none;opacity:1}}.lightSpeedIn{animation-name:lightSpeedIn;animation-timing-function:ease-out}@keyframes lightSpeedOut{0%{opacity:1}100%{transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{animation-name:lightSpeedOut;animation-timing-function:ease-in}@keyframes rotateIn{0%{transform-origin:center;transform:rotate3d(0,0,1,-200deg);opacity:0}100%{transform-origin:center;transform:none;opacity:1}}.rotateIn{animation-name:rotateIn}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;transform:rotate3d(0,0,1,-45deg);opacity:0}100%{transform-origin:left bottom;transform:none;opacity:1}}.rotateInDownLeft{animation-name:rotateInDownLeft}@keyframes rotateInDownRight{0%{transform-origin:right bottom;transform:rotate3d(0,0,1,45deg);opacity:0}100%{transform-origin:right bottom;transform:none;opacity:1}}.rotateInDownRight{animation-name:rotateInDownRight}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;transform:rotate3d(0,0,1,45deg);opacity:0}100%{transform-origin:left bottom;transform:none;opacity:1}}.rotateInUpLeft{animation-name:rotateInUpLeft}@keyframes rotateInUpRight{0%{transform-origin:right bottom;transform:rotate3d(0,0,1,-90deg);opacity:0}100%{transform-origin:right bottom;transform:none;opacity:1}}.rotateInUpRight{animation-name:rotateInUpRight}@keyframes rotateOut{0%{transform-origin:center;opacity:1}100%{transform-origin:center;transform:rotate3d(0,0,1,200deg);opacity:0}}.rotateOut{animation-name:rotateOut}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}100%{transform-origin:left bottom;transform:rotate(0,0,1,45deg);opacity:0}}.rotateOutDownLeft{animation-name:rotateOutDownLeft}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}100%{transform-origin:right bottom;transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutDownRight{animation-name:rotateOutDownRight}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}100%{transform-origin:left bottom;transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutUpLeft{animation-name:rotateOutUpLeft}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}100%{transform-origin:right bottom;transform:rotate3d(0,0,1,90deg);opacity:0}}.rotateOutUpRight{animation-name:rotateOutUpRight}@keyframes hinge{0%{transform-origin:top left;animation-timing-function:ease-in-out}20%,60%{transform:rotate3d(0,0,1,80deg);transform-origin:top left;animation-timing-function:ease-in-out}40%,80%{transform:rotate3d(0,0,1,60deg);transform-origin:top left;animation-timing-function:ease-in-out;opacity:1}100%{transform:translate3d(0,700px,0);opacity:0}}.hinge{animation-name:hinge}@keyframes rollIn{0%{opacity:0;transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg)}100%{opacity:1;transform:none}}.rollIn{animation-name:rollIn}@keyframes rollOut{0%{opacity:1}100%{opacity:0;transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg)}}.rollOut{animation-name:rollOut}@keyframes zoomIn{0%{opacity:0;transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{animation-name:zoomIn}@keyframes zoomInDown{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{animation-name:zoomInDown}@keyframes zoomInLeft{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{animation-name:zoomInLeft}@keyframes zoomInRight{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{animation-name:zoomInRight}@keyframes zoomInUp{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{animation-name:zoomInUp}@keyframes zoomOut{0%{opacity:1}50%{opacity:0;transform:scale3d(.3,.3,.3)}100%{opacity:0}}.zoomOut{animation-name:zoomOut}@keyframes zoomOutDown{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}100%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{animation-name:zoomOutDown}@keyframes zoomOutLeft{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}100%{opacity:0;transform:scale(.1) translate3d(-2000px,0,0);transform-origin:left center}}.zoomOutLeft{animation-name:zoomOutLeft}@keyframes zoomOutRight{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}100%{opacity:0;transform:scale(.1) translate3d(2000px,0,0);transform-origin:right center}}.zoomOutRight{animation-name:zoomOutRight}@keyframes zoomOutUp{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}100%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{animation-name:zoomOutUp} .animated.-half-second { animation-duration: 0.5s; } .slide-in-left.ng-hide-remove { - -webkit-animation: fadeInLeft 0.6s; animation: fadeInLeft 0.6s; } .slide-in-left.ng-hide-add { - -webkit-animation: fadeOutLeft 0.6s; animation: fadeOutLeft 0.6s; display: block !important; } .slide-in-right.ng-hide-remove { - -webkit-animation: fadeInRight 0.6s; animation: fadeInRight 0.6s; } .slide-in-right.ng-hide-add { - -webkit-animation: fadeOutRight 0.6s; animation: fadeOutRight 0.6s; display: block !important; } .slide-in-up.ng-hide-remove { - -webkit-animation: fadeInUp 0.6s; animation: fadeInUp 0.6s; } .slide-in-up.ng-hide-add { - -webkit-animation: fadeOutDown 0.6s; animation: fadeOutDown 0.6s; display: block !important; } @@ -60,70 +54,10 @@ color: @red !important; } -@-webkit-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} -@-moz-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} -@-ms-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} -@-o-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} @keyframes leave { to { opacity: 0; - height: 0px; + height: 0; bottom: -70px; } 25% { @@ -132,26 +66,18 @@ from { opacity: 1; height: 30px; - bottom: 0px; + bottom: 0; } } .tree-node-slide-up { opacity:1; - top: 0px; - -webkit-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; - -moz-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; - -ms-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; - -o-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; + top: 0; transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; } .tree-node-slide-up * { font-size:100%; - -webkit-transition:font-size 700ms; - -moz-transition:font-size 700ms; - -ms-transition:font-size 700ms; - -o-transition:font-size 700ms; transition:font-size 700ms; } .tree-node-slide-up.tree-node-slide-up-hide-active { @@ -166,10 +92,6 @@ .tree-fade-out-show, .tree-fade-out-hide div:not(.tree-node-slide-up-hide-active), .tree-fade-out-show div:not(.tree-node-slide-up-hide-active) { - -webkit-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; - -moz-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; - -ms-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; - -o-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; } .tree-fade-out-show.tree-fade-out-show-active div:not(.tree-node-slide-up-hide-active){ 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 073d1b5721..0c6e790272 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -62,10 +62,6 @@ body.umb-drawer-is-visible #mainwrapper{ margin: 0; } -#umb-notifications-wrapper { - left: 80px; -} - #contentcolumn { left: 0px; } @@ -171,13 +167,11 @@ body.umb-drawer-is-visible #mainwrapper{ } @media (min-width: 1101px) { - #contentwrapper, #umb-notifications-wrapper {left: 360px;} - #speechbubble {left: 360px;} + #contentwrapper, #speechbubble {left: 360px;} .emptySection #contentwrapper {left:0px;} } //empty section modification -.emptySection #umb-notifications-wrapper {left: 80px;} .emptySection #speechbubble {left: 0;} .emptySection #navigation {display: none} diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 78e9cabcfc..88988485fe 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -76,7 +76,6 @@ @import "listview.less"; @import "gridview.less"; @import "footer.less"; -@import "dashboards.less"; @import "forms/umb-validation-label.less"; @@ -91,6 +90,7 @@ @import "components/application/umb-dashboard.less"; @import "components/html/umb-expansion-panel.less"; +@import "components/html/umb-group-panel.less"; @import "components/html/umb-alert.less"; @import "components/tree/umb-tree.less"; @@ -105,6 +105,7 @@ @import "components/editor/umb-editor.less"; @import "components/umb-sub-views.less"; @import "components/umb-editor-navigation.less"; +@import "components/umb-editor-navigation-item.less"; @import "components/umb-editor-sub-views.less"; @import "components/editor/subheader/umb-editor-sub-header.less"; @import "components/umb-flatpickr.less"; @@ -116,6 +117,7 @@ @import "components/umb-confirm-action.less"; @import "components/umb-keyboard-shortcuts-overview.less"; @import "components/umb-checkbox-list.less"; +@import "components/umb-form-check.less"; @import "components/umb-locked-field.less"; @import "components/umb-tabs.less"; @import "components/umb-load-indicator.less"; @@ -136,6 +138,7 @@ @import "components/umb-insert-code-box.less"; @import "components/umb-packages.less"; @import "components/umb-package-local-install.less"; +@import "components/umb-panel-group.less"; @import "components/umb-lightbox.less"; @import "components/umb-avatar.less"; @import "components/umb-progress-bar.less"; @@ -190,18 +193,20 @@ @import "pages/welcome-dashboard.less"; -//used for property editors +// Used for property editors @import "property-editors.less"; -//used for prevalue editors +// Used for prevalue editors @import "components/prevalues/multivalues.less"; +// Dashboards +@import "dashboards/getstarted.less"; +@import "dashboards/umbraco-forms.less"; +@import "dashboards/examine-management.less"; +@import "dashboards/healthcheck.less"; @import "typeahead.less"; @import "hacks.less"; -@import "healthcheck.less"; -@import "getstarted.less"; - // cleanup properties.less when it is done @import "properties.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/button-groups.less b/src/Umbraco.Web.UI.Client/src/less/button-groups.less index 070e353c14..444a56803c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/button-groups.less +++ b/src/Umbraco.Web.UI.Client/src/less/button-groups.less @@ -7,11 +7,9 @@ .btn-group { position: relative; display: inline-block; - .ie7-inline-block(); font-size: 0; // remove as part 1 of font-size inline-block hack vertical-align: middle; // match .btn alignment given font-size hack above white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page) - .ie7-restore-left-whitespace(); } // Space out series of button groups @@ -104,24 +102,14 @@ padding-left: 8px; padding-right: 8px; .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); - *padding-top: 5px; - *padding-bottom: 5px; } .btn-group > .btn-mini + .dropdown-toggle { padding-left: 5px; padding-right: 5px; - *padding-top: 2px; - *padding-bottom: 2px; -} -.btn-group > .btn-small + .dropdown-toggle { - *padding-top: 5px; - *padding-bottom: 4px; } .btn-group > .btn-large + .dropdown-toggle { padding-left: 12px; padding-right: 12px; - *padding-top: 7px; - *padding-bottom: 7px; } .btn-group.open { @@ -182,28 +170,11 @@ } -/* -// Account for other colors -.btn-primary, -.btn-warning, -.btn-danger, -.btn-info, -.btn-success, -.btn-inverse { - .caret { - border-top-color: @white; - border-bottom-color: @white; - } -} -*/ - - // Vertical button groups // ---------------------- .btn-group-vertical { display: inline-block; // makes buttons only take up the width they need - .ie7-inline-block(); } .btn-group-vertical > .btn { display: block; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index c6a8447342..f21c7f3106 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -62,7 +62,6 @@ .btn-group>.btn+.dropdown-toggle { box-shadow: none; - -webkit-box-shadow:none; border-left-width: 1px; border-left-style: solid; border-color: rgba(0,0,0,0.09); @@ -139,7 +138,6 @@ input[type="button"] { line-height: 32px; text-align: center; - -moz-border-radius: 15px; border-radius: 15px; height: 32px; width: 32px; @@ -191,13 +189,13 @@ input[type="button"] { .btn-success { .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight, @btnSuccessType); } -// Info appears as a neutral blue +// Info appears as a sand color .btn-info { - .buttonBackground(@sand-5, @blueDark, @blueExtraDark, @u-white); + .buttonBackground(@sand-5, @sand-6, @blueExtraDark, @blueMid); } // Made for Umbraco, 2019 .btn-action { - .buttonBackground(@blueExtraDark, @blueDark, @pinkLight, @u-white); + .buttonBackground(@blueExtraDark, @blueDark, @white, @u-white); } // Made for Umbraco, 2019 .btn-selection { @@ -236,11 +234,11 @@ input[type="button"] { padding: 15px 50px; font-size: 16px; border: none; - background: @green; + background: @ui-btn-positive; color: @white; font-weight: bold; &:hover { - background: @green-d1; + background: @ui-btn-positive-hover; } } @@ -274,23 +272,6 @@ input[type="submit"].btn { border: 0; } - // IE7 has some default padding on button controls - *padding-top: 3px; - *padding-bottom: 3px; - - &.btn-large { - *padding-top: 7px; - *padding-bottom: 7px; - } - &.btn-small { - *padding-top: 3px; - *padding-bottom: 3px; - } - &.btn-mini { - *padding-top: 1px; - *padding-bottom: 1px; - } - // Safari defaults to 1px for input. Ref U4-7721. margin: 0px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 7f67d5c3b2..7440a5723a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -20,12 +20,10 @@ body { position: absolute; padding: 0; margin: 0; - font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; color: #343434; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; padding-left:80px; } @@ -106,35 +104,11 @@ a, a:hover{ cursor: pointer; background: #f2f2f2; border: 1px solid #cccccc; - -webkit-border-radius: 2px; - -moz-border-radius: 2px; border-radius: 2px; - -webkit-box-shadow: none; box-shadow: none; } -/* -.btn-success { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #53a93f; - background-image: -moz-linear-gradient(top, #53a93f, #53a93f); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#53a93f), to(#53a93f)); - background-image: -webkit-linear-gradient(top, #53a93f, #53a93f); - background-image: -o-linear-gradient(top, #53a93f, #53a93f); - background-image: linear-gradient(to bottom, #53a93f, #53a93f); - background-repeat: repeat-x; - border-color: #53a93f #53a93f #38712a; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff53a93f', endColorstr='#ff53a93f', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} -.btn-success:hover { - color: #ffffff; -} -*/ .btn-group > .btn + .dropdown-toggle { - -webkit-box-shadow: none; box-shadow: none; } @@ -145,18 +119,12 @@ a, a:hover{ .btn-group > .btn + .dropdown-toggle { padding-right: 8px; padding-left: 8px; - -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); } .btn-group > .btn:last-child, .btn-group > .dropdown-toggle { - -webkit-border-top-right-radius: 2px; border-top-right-radius: 2px; - -webkit-border-bottom-right-radius: 2px; border-bottom-right-radius: 2px; - -moz-border-radius-topright: 2px; - -moz-border-radius-bottomright: 2px; } .caret { @@ -176,6 +144,7 @@ a, a:hover{ .dropdown-menu { position: absolute; + display: block; top: auto; right: 0; z-index: 1000; @@ -189,14 +158,8 @@ a, a:hover{ background-color: #ffffff; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-border-radius: 6px; - -moz-border-radius: 6px; border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; background-clip: padding-box; } @@ -276,8 +239,6 @@ a, a:hover{ font-size: 13px; line-height: 16px; background: #1b264f; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; z-index: 9999; } @@ -299,8 +260,6 @@ a, a:hover{ text-align: center; color: #D8D7D9; opacity: 0.4; - -webkit-transition: all .3s linear; - -moz-transition: all .3s linear; transition: all .3s linear; } @@ -311,8 +270,6 @@ ul.sections { position:absolute; top: 90px; width: 80px; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; list-style:none; margin:0; @@ -323,8 +280,6 @@ ul.sections { ul.sections li { display: block; border-left: 4px #1b264f solid; - -webkit-transition: all .3s linear; - -moz-transition: all .3s linear; transition: all .3s linear; } @@ -332,8 +287,6 @@ ul.sections li { .fix-left-menu ul.sections li a i { color: #fff; opacity: .7; - -webkit-transition: all .3s linear; - -moz-transition: all .3s linear; transition: all .3s linear; } @@ -400,15 +353,11 @@ ul.sections li.current, ul.sections li:hover { font-size: 13px; line-height: 16px; background: #ffffff; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; width: 250px; height: 100%; padding: 0; z-index: 999; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); } @@ -429,8 +378,6 @@ ul.sections li.current, ul.sections li:hover { position: absolute; right: 20px; cursor:pointer; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } @@ -468,11 +415,7 @@ ul.sections li.current, ul.sections li:hover { font-size: 13px; line-height: 16px; background: #ffffff; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } @@ -628,7 +571,7 @@ h4.panel-title { .canvasdesigner .ui-widget-content { background: rgba(0, 0, 0, 0.27) !important; - border: 0px solid #fff !important; + border: 0 solid #fff !important; border-radius: 1px !important; } @@ -722,10 +665,6 @@ h4.panel-title { cursor: pointer; } -.canvasdesigner .spectrumcolorpicker { - -} - .canvasdesigner .float-panel .sp-container.sp-flat { background-color: transparent; border: none; @@ -826,9 +765,7 @@ iframe { } .flip:before { - -webkit-transform: rotate(90deg); - -moz-transform: rotate(90deg); - -o-transform: rotate(90deg); + transform: rotate(90deg); } /*************************************************/ @@ -895,9 +832,6 @@ iframe { height: 78px; cursor: pointer; background-position: center center; - -webkit-background-size: cover; - -moz-background-size: cover; - -o-background-size: cover; background-size: cover; border: 2px solid #F3F2F2; } @@ -969,7 +903,6 @@ iframe { font-size: 23px; cursor: pointer; opacity: 0; - filter: alpha(opacity=0); transform: translate(-300px, 0) scale(4); direction: ltr; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less index c950b01602..527c742382 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less @@ -57,7 +57,6 @@ } .umb-language-picker__dropdown a.umb-language-picker__dropdown-item--current { - //background-color: @gray-10; padding-left: 16px; border-left: 4px solid @ui-light-active-border; color:@ui-light-active-type; 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 d576c33b67..0770a895c0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -63,24 +63,37 @@ // header .umb-editor-header { - background: @white; - position: absolute; - padding-left: 20px; - z-index: @zIndexEditor; - border-bottom: 1px solid @gray-9; - width: 100%; - box-sizing: border-box; - height: @editorHeaderHeight; + background: @white; + position: absolute; + padding-left: 20px; + z-index: @zIndexEditor; + border-bottom: 1px solid @gray-9; + width: 100%; + box-sizing: border-box; + height: @editorHeaderHeight; } .umb-editor-header__back { color: @gray-6; + margin-bottom: 1px; transition: color 0.1s ease-in-out; } .umb-editor-header__name-wrapper { position: relative; display: flex; + border: 1px solid @inputBorder; + &:focus-within { + border-color: @inputBorderFocus; + } +} + +.umb-editor-header__name-and-description { + margin-right: 20px; +} + +.-split-view-active .umb-editor-header__name-and-description { + margin-right: 0; } .umb-editor-header__name-wrapper ng-form { @@ -95,12 +108,14 @@ input.umb-editor-header__name-input { margin-bottom: 0; font-weight: bold; box-sizing: border-box; - height: 32px; + height: 30px; line-height: 32px; width: 100%; padding: 0 10px; background: @white; - border: 1px solid @gray-8; + &:focus { + border-color: transparent; + } } input.umb-editor-header__name-input:disabled { @@ -147,7 +162,6 @@ a.umb-editor-header__close-split-view:hover { align-items: center; padding: 0 10px; margin: 1px 1px; - position: absolute; right: 0; height: 30px; text-decoration: none !important; 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 2b9f1e31a5..0c11349841 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 @@ -7,6 +7,7 @@ margin-top: -10px; position: relative; top: 0; + box-sizing: border-box; &.nested { margin-top: 0; @@ -15,6 +16,13 @@ } } +.umb-editor-sub-header.--state-selection { + padding-left: 10px; + padding-right: 10px; + background-color: @pinkLight; + border-radius: 3px; +} + .umb-editor-sub-header.-umb-sticky-bar { box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); transition: box-shadow 1s; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index 764094a9b8..a9d879ab7f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -4,6 +4,7 @@ right: 0; bottom: 0; left: 0; + overflow: hidden; } .umb-editor { @@ -16,21 +17,55 @@ z-index: @zIndexEditor; } -.umb-editor--animating { - will-change: transform, opacity, width, left; +.umb-editor--infinityMode { + transform: none; + will-change: transform; + transition: transform 400ms ease-in-out; + &.moveRight { + transform: translateX(110%); + } +} + +.umb-editor--outOfRange { + transform: none; + display: none; + will-change: auto; + transition: display 0s 320ms; +} +.umb-editor--level0 { + transform: none; +} +.umb-editor--level1 { + transform: translateX(60px); +} +.umb-editor--level2 { + transform: translateX(120px); +} +.umb-editor--level3 { + transform: translateX(180px); +} + +.umb-editor--n1 { + right:60px; +} +.umb-editor--n2 { + right:120px; +} +.umb-editor--n3 { + right:180px; } // hide all infinite editors by default // will be shown through animation .umb-editors .umb-editor { - opacity: 0; box-shadow: 0px 0 30px 0 rgba(0,0,0,.3); } .umb-editor--small { width: 500px; + will-change: transform; left: auto; - + .umb-editor-container { max-width: 500px; } @@ -44,4 +79,14 @@ left: 0; background: rgba(0,0,0,0.4); z-index: @zIndexEditor; + visibility: hidden; + opacity: 0; + transition: opacity 320ms 20ms, visibility 0s 400ms; +} + +#contentcolumn > .umb-editor__overlay, +.--notInFront .umb-editor__overlay { + visibility: visible; + opacity: 1; + transition: opacity 320ms 20ms, visibility 0s; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less new file mode 100644 index 0000000000..15f85e1c77 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less @@ -0,0 +1,21 @@ +.umb-group-panel { + background: @white; + border-radius: 3px; + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.16); + margin-bottom: 20px; +} + +.umb-group-panel__header { + padding: 12px 20px; + font-weight: bold; + font-size: 16px; + display: flex; + align-items: center; + justify-content: space-between; + color: @grayDark; + border-bottom: 1px solid @gray-9; +} + +.umb-group-panel__content { + padding: 20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less index b29c340dce..9e16eca414 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less @@ -1,35 +1,31 @@ .umb-notifications { - z-index: 1000; - position: absolute; - bottom: @editorFooterHeight; - left: 0; - right: 0; - border-bottom: none; - margin: auto; - padding: 0px; - border: none; - background: none; - border-radius: 0; + z-index: 1000; + position: absolute; + bottom: @editorFooterHeight; + left: 0; + right: 0; + + @media (min-width: 1101px) { + left: 360px; + } } .umb-notifications__notifications { - list-style: none; - margin: 0; - position: relative; -} - + list-style: none; + margin: 0; + position: relative; +} .umb-notifications__notification { - padding: 5px 20px; - text-shadow: none; - font-size: 14px; - border: none; - border-radius: 0; - position: relative; - margin-bottom: 0; + padding: 5px 20px; + text-shadow: none; + font-size: 14px; + border: none; + position: relative; + margin-bottom: 0; } .umb-notifications__notification.-extra-padding { - padding-top: 20px; - padding-bottom: 20px; + padding-top: 20px; + padding-bottom: 20px; } 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 6e9c79a0c2..9205dc9c5f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -250,3 +250,11 @@ .form-horizontal .umb-overlay .controls { margin-left: 0 !important; } + +.umb-overlay .text-error { + color: @formErrorText; +} + +.umb-overlay .text-success { + color: @formSuccessText; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less index 40a0cc41e3..14544ded10 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less @@ -23,7 +23,6 @@ .icon { font-size: 18px; vertical-align: middle; - //color: @gray-3; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less index 53e724cf54..b372d41eb4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -46,11 +46,7 @@ } // active is equivilant to selected, its the item that is begin affected by the actions performed in the right-click-dialog. -.umb-tree-item.active { - -} .umb-tree-item.active > .umb-tree-item__inner { - //background: @ui-selected; color: @ui-selected-type; a { color: @ui-selected-type; @@ -68,8 +64,6 @@ border: 2px solid fade(white, 80%); } &:hover { - //background: @ui-selected-hover; - //border-color: @ui-selected-border-hover;// dont hover border, cause its cant be deselected in current code. color: @ui-selected-type-hover; a { color: @ui-selected-type-hover; @@ -95,7 +89,6 @@ i { background: @ui-active-type; - //border-color: @ui-active; transition: opacity 120ms ease; } } @@ -104,14 +97,10 @@ .umb-tree-icon, ins { color: @ui-active-type !important; - //background-color: @ui-active; - //border-color: @ui-active; } } .umb-tree-item.current-not-active > .umb-tree-item__inner { - background: @ui-active-blur; - color:@ui-active-type; - + color:@ui-active-type; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 698a4211f5..b66ab40335 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -14,11 +14,6 @@ a, a:hover { outline: none; text-decoration: none; - - // TODO: => confirm not in use -// &.noSpr { -// background-position: 0 -// } } ins { @@ -31,7 +26,6 @@ transition: color 120ms; &:hover { - //opacity: .7; color: @ui-option-type-hover; } } @@ -171,8 +165,10 @@ body.touch .umb-tree { } } -.umb-tree .umb-tree-node-checked i[class^="icon-"], -.umb-tree .umb-tree-node-checked i[class*=" icon-"] { +.umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class^="icon-"], +.umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class*=" icon-"], +.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class^="icon-"], +.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class*=" icon-"] { font-family: 'icomoon' !important; color: @green !important; @@ -219,56 +215,53 @@ body.touch .umb-tree { // Tree item states // ------------------------- .not-published { - > i.icon, - a { + > .umb-tree-item__inner > i.icon, + > .umb-tree-item__inner > a { opacity: 0.6; } } .not-allowed { - > i.icon, - a { + > .umb-tree-item__inner > i.icon, + > .umb-tree-item__inner > a { cursor: not-allowed; } } -.protected, -.has-unpublished-version, -.is-container, -.locked { +.umb-tree-item__annotation { &::before { font-family: 'icomoon'; position: absolute; - font-size: 20px; - padding-left: 7px; - padding-top: 7px; bottom: 0; } } -.protected::before { - content: "\e256"; - color: @red; -} - -.has-unpublished-version::before { +.has-unpublished-version > .umb-tree-item__inner > .umb-tree-item__annotation::before { content: "\e25a"; color: @green; + font-size: 20px; + margin-left: -25px; } -.is-container::before { +.is-container > .umb-tree-item__inner > .umb-tree-item__annotation::before { content: "\e04e"; color: @blue; - font-size: 8px; - padding-left: 13px; - padding-top: 8px; - pointer-events: none; + font-size: 9px; + margin-left: -20px; } +.protected > .umb-tree-item__inner > .umb-tree-item__annotation::before { + content: "\e256"; + color: @red; + font-size: 20px; + margin-left: -25px; +} -.locked::before { +.locked > .umb-tree-item__inner > .umb-tree-item__annotation::before { content: "\e0a7"; color: @red; + font-size: 9px; + margin-left: -20px; } .no-access { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less index 02f30f6f35..985e57bea5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less @@ -1,3 +1,6 @@ +@checkboxWidth: 15px; +@checkboxHeight: 15px; + .umb-checkbox-list { list-style: none; margin-left: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index ccf86e7ea6..d2fa2be0c7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -10,18 +10,13 @@ flex: 0 1 200px; cursor: pointer; position: relative; - //margin: 10px; user-select: none; box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); border-radius: 3px; } -.umb-content-grid__item.-selected { - //box-shadow: 0 2px 8px 0 rgba(0,0,0,0.35); - - //color:@ui-selected-type; - +.umb-content-grid__item.-selected { &::before { content: ""; position: absolute; @@ -49,15 +44,9 @@ .umb-content-grid__icon[class^="icon-"], .umb-content-grid__icon[class*=" icon-"] { font-size: 20px; -//color: @gray-8; margin-right: 5px; } -.umb-content-grid__icon.-light { - //color: @gray-5; -} - - .umb-content-grid__content { box-sizing: border-box; padding: 15px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less new file mode 100644 index 0000000000..e6b3fdbfa9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less @@ -0,0 +1,172 @@ +.umb-sub-views-nav-item { + position: relative; + display: block; +} +.umb-sub-views-nav-item > a { + text-align: center; + cursor: pointer; + display: block; + padding: 4px 10px 0 10px; + min-width: 70px; + border-right: 1px solid @gray-9; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: calc(~'@{editorHeaderHeight}' - ~'1px'); // need to offset the 1px border-bottom on .umb-editor-header - avoids overflowing top of the container + position: relative; + + color: @ui-active-type; + + &:hover { + color: @ui-active-type-hover !important; + } + + &::after { + content: ""; + height: 0px; + left: 8px; + right: 8px; + background-color: @ui-light-active-border; + position: absolute; + bottom: 0; + border-radius: 3px 3px 0 0; + opacity: 0; + transition: all .2s linear; + } +} + +.umb-sub-views-nav-item > a:active { + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); +} +.umb-sub-views-nav-item > a:focus { + outline: none; +} + +.umb-sub-views-nav-item > a:hover, +.umb-sub-views-nav-item > a:focus { + text-decoration: none; +} + +.umb-sub-views-nav-item > a.is-active { + + color: @ui-light-active-type; + + &::after { + opacity: 1; + height: 4px; + } +} + +.show-validation .umb-sub-views-nav-item > a.-has-error { + color: @red; + &::after { + background-color: @red; + } +} + +.umb-sub-views-nav-item .icon { + font-size: 24px; + display: block; + text-align: center; + margin-bottom: 7px; +} + +.umb-sub-views-nav-item .badge { + position: absolute; + top: 6px; + right: 6px; + min-width: 16px; + color: @white; + background-color: @ui-active-type; + border: 2px solid @white; + border-radius: 50%; + font-size: 10px; + font-weight: bold; + padding: 2px; + line-height: 16px; + display: block; + + &.-type-alert { + background-color: @red; + } + &.-type-warning { + background-color: @yellow-d2; + } + &:empty { + height: 12px; + min-width: 12px; + } +} + +.umb-sub-views-nav-item-text { + font-size: 12px; + line-height: 1em; +} + + +.umb-sub-views-nav-item__anchor_dropdown {// inherits from .dropdown-menu + display: block; + margin: 0; + overflow: hidden; + + // center align horizontal + left: 50%; + transform: translateX(-50%); + + visibility:hidden; + opacity: 0; + transition: visibility 0s 500ms, opacity 250ms 250ms; +} +.umb-sub-views-nav-item__anchor_dropdown li a { + border-left: 4px solid transparent; +} +.umb-sub-views-nav-item__anchor_dropdown li.is-active a { + border-left-color: @ui-selected-border; +} + +.umb-sub-views-nav-item:hover .umb-sub-views-nav-item__anchor_dropdown { + visibility:visible; + opacity: 1; + transition: visibility 0s 0s, opacity 20ms 0s; +} + + + +// -------------------------------- +// item__more, appears when there is not enough room for the visible items. +// -------------------------------- + +.umb-sub-views-nav-item-more__icon { + margin-bottom: 10px; +} + +.umb-sub-views-nav-item-more__icon i { + height: 5px; + width: 5px; + border-radius: 50%; + background: @ui-active-type;// fallback if browser doesnt support currentColor + background: currentColor; + display: inline-block; + margin: 0 5px 0 0; +} + +.umb-sub-views-nav-item-more__icon i:last-of-type { + margin-right: 0; +} + +.umb-sub-views-nav-item-more__dropdown { + left: auto; + right: 0; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + min-width: auto; + margin-top: 10px; +} +.umb-sub-views-nav-item-more__dropdown > li { + display: flex; +} +.umb-sub-views-nav-item-more__dropdown .umb-sub-views-nav-item:first { + border-left: none; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less index c1f099c2a5..985765e53a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less @@ -4,135 +4,3 @@ margin: 0; border-left: 1px solid @gray-9; } - -.umb-sub-views-nav-item { - text-align: center; - cursor: pointer; - display: block; - padding: 4px 10px 0 10px; - //border-bottom: 4px solid transparent; - min-width: 70px; - border-right: 1px solid @gray-9; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: @editorHeaderHeight; - position: relative; - - color: @ui-active-type; - - &:hover { - color: @ui-active-type-hover !important; - } - - &::after { - content: ""; - height: 0px; - left: 8px; - right: 8px; - background-color: @ui-light-active-border; - position: absolute; - bottom: 0; - border-radius: 3px 3px 0 0; - opacity: 0; - transition: all .2s linear; - } -} - -.umb-sub-views-nav-item:focus { - outline: none; -} - -.umb-sub-views-nav-item:hover, -.umb-sub-views-nav-item:focus { - text-decoration: none; -} - -.umb-sub-views-nav-item.is-active { - //color: @ui-active; - //border-bottom-color: @ui-active; - - //background-color: rgba(@ui-active, 0.25); - color: @ui-light-active-type; - //border-bottom-color: @ui-active; - &::after { - opacity: 1; - height: 4px; - } -} - -.show-validation .umb-sub-views-nav-item.-has-error { - color: @red; -} - -.umb-sub-views-nav-item .icon { - font-size: 24px; - display: block; - text-align: center; - margin-bottom: 7px; -} - -.umb-sub-views-nav-item .badge { - position: absolute; - top: 6px; - right: 6px; - min-width: 16px; - color: @white; - background-color: @ui-active-type; - border: 2px solid @white; - border-radius: 50%; - font-size: 10px; - font-weight: bold; - padding: 2px; - line-height: 16px; - display: block; - - &.-type-alert { - background-color: @red; - } - &.-type-warning { - background-color: @yellow-d2; - } - &:empty { - height: 12px; - min-width: 12px; - } -} - -.umb-sub-views-nav-item-text { - font-size: 12px; - line-height: 1em; -} - -.umb-sub-views-nav-item__more { - margin-bottom: 10px; -} - -.umb-sub-views-nav-item__more i { - height: 5px; - width: 5px; - border-radius: 50%; - background: @gray-3; - display: inline-block; - margin: 0 5px 0 0; -} - -.umb-sub-views-nav-item__more i:last-of-type { - margin-right: 0; -} - -// make dots green the an item is active -.umb-sub-views-nav-item.is-active .umb-sub-views-nav-item__more i { - background-color: @ui-active; -} - -.umb-sub-views-nav__dropdown.umb-sub-views-nav__dropdown { - left: auto; - right: 0; - display: grid; - grid-template-columns: 1fr 1fr 1fr; - min-width: auto; - margin-top: 10px; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less index 63190f0ed3..b5021d7c38 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -13,8 +13,6 @@ color: @gray-3; margin: 0 0 20px 0; position: relative; - -webkit-transition: height 0.8s; - -moz-transition: height 0.8s; transition: height 0.8s; .illustration { width: 300px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less index 32100247a0..ffdd4d9322 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -14,7 +14,6 @@ padding: 10px 20px; box-sizing: border-box; flex: 1 1 200px; - //border: 2px solid transparent; transition: border 0.2s; position: relative; justify-content: space-between; @@ -27,11 +26,7 @@ } .umb-folder-grid__folder.-selected { - //box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); - - //background: @ui-selected; color:@ui-selected-type; - //border-color:@ui-selected-border; &::before { content: ""; @@ -49,7 +44,6 @@ &:hover { color:@ui-selected-type-hover; - //border-color:@ui-selected-border-hover; } } @@ -73,37 +67,13 @@ .umb-folder-grid__folder-icon[class*=" icon-"] { font-size: 20px; margin-right: 15px; - //color: @black; } .umb-folder-grid__folder-name { font-size: 13px; - //color: @black; font-weight: bold; } .umb-folder-grid__folder-name:hover { text-decoration: underline; } -/* -.umb-folder-grid__action { - opacity: 0; - border: 2px solid @white; - width: 26px; - height: 26px; - background: @gray-3; - border-radius: 50px; - box-sizing: border-box; - display: flex; - justify-content: center; - align-items: center; - color: @white; - margin-left: 7px; - cursor: pointer; -} - -.umb-folder-grid__action.-selected { - opacity: 1; - background: @green; -} -*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less new file mode 100644 index 0000000000..fbc9cd8f97 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -0,0 +1,136 @@ +@checkboxWidth: 16px; +@checkboxHeight: 16px; + +.umb-form-check { + display: flex; + flex-wrap: wrap; + align-items: center; + position: relative; + padding: 0; + margin: 0; + min-height: 22px; + line-height: 22px; + cursor: pointer !important; + + &__text { + margin: 0 0 0 26px; + position: relative; + top: 0; + } + + &__input { + position: absolute; + top: 0; + left: 0; + opacity: 0; + + &:checked ~ .umb-form-check__state .umb-form-check__check { + border-color: @ui-option-type; + } + + &:focus:checked ~ .umb-form-check .umb-form-check__check, + &:focus ~ .umb-form-check__state .umb-form-check__check { + border-color: @inputBorderFocus; + } + + &:checked ~ .umb-form-check__state { + .umb-form-check__check { + // This only happens if the state has a radiobutton modifier + .umb-form-check--radiobutton & { + &:before { + opacity: 1; + transform: scale(1); + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + &:before { + width: @checkboxWidth; + height: @checkboxHeight; + } + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + .umb-form-check__icon { + opacity: 1; + } + } + } + } + + &__state { + display: flex; + height: 18px; + margin-top: 2px; + position: absolute; + top: 0; + left: 0; + } + + &__check { + display: flex; + position: relative; + background: @white; + border: 1px solid @inputBorder; + width: @checkboxWidth; + height: @checkboxHeight; + + &:before { + content: ""; + background: @ui-option-type; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + } + // This only happens if state has the radiobutton modifier + .umb-form-check--radiobutton & { + border-radius: 100%; + + &:before { + width: 10px; + height: 10px; + border-radius: 100%; + opacity: 0; + transform: scale(0); + transition: .15s ease-out; + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + &:before { + width: 0; + height: 0; + transition: .05s ease-out; + } + } + } + + &__icon { + color: @white; + text-align: center; + font-size: 12px; + opacity: 0; + transition: .2s ease-out; + + &:before { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + margin: auto; + } + } + + &.umb-form-check--disabled { + cursor: not-allowed !important; + opacity: 0.5; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index e7f24d08fa..d6db204ff2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -604,7 +604,6 @@ -webkit-appearance: none; background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); background-repeat: repeat-x; - filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0); zoom: 1; border-color: #bfbfbf #bfbfbf #999; border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); @@ -836,8 +835,6 @@ .preview-rows { display: inline-block; position: relative; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; width: 125px; margin: 15px; @@ -901,8 +898,6 @@ .preview-col { display: block; float: left; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; width: 33.3%; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index 331fd9eb66..d8c7224d57 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -16,10 +16,6 @@ box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); } -.umb-group-builder__group.-active { - border-color: @ui-active; -} - .umb-group-builder__group.-inherited { border-color: @gray-9; animation: fadeIn 0.5s; @@ -33,6 +29,7 @@ align-items: center; cursor: pointer; border: 1px dashed @gray-7; + background-color: transparent; color: @ui-action-type; font-weight: bold; position: relative; @@ -253,24 +250,38 @@ input.umb-group-builder__group-title-input:disabled:hover { } .umb-group-builder__property-preview { - flex: 1; - height: 30px; - overflow: hidden; - position: relative; - padding: 35px 10px 25px 10px; - background-color: @gray-10; + flex: 1; + height: 30px; + overflow: hidden; + position: relative; + padding: 35px 10px 25px 10px; + border-radius: 3px; + cursor: pointer; + + &::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba(0,0,0,0.1); + transition: opacity 120ms; + } } .umb-group-builder__property-preview:hover { - cursor: pointer; + &::after { + opacity: .8; + } } .umb-group-builder__property-preview:focus { - outline: none; + outline: none; } -.umb-group-builder__property-preview.-not-clickable:hover { - cursor: auto; +.umb-group-builder__property-preview.-not-clickable { + cursor: auto; } .umb-group-builder__property-preview .help-inline { @@ -312,8 +323,8 @@ input.umb-group-builder__group-title-input:disabled:hover { .umb-group-builder__property-tags { position: absolute; z-index: 20; - top: 0; - left: 0; + top: 4px; + left: 4px; display: flex; flex-direction: row; } @@ -326,9 +337,10 @@ input.umb-group-builder__group-title-input:disabled:hover { .umb-group-builder__property-tag { font-size: 12px; background-color: @gray-8; - margin-left: 10px; + margin-left: 4px; padding: 0 4px; display: flex; + border-radius: 3px; } .umb-group-builder__property-tag:first-child { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index fc6de2bf75..7a6cd043a4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -217,4 +217,6 @@ .umb-media-grid__item:hover .umb-media-grid__edit { opacity: 1; - } + text-decoration: none; + box-shadow: 0 1px 2px rgba(0,0,0,.2); +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 52cc7a9aaf..6af0641d8c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -26,6 +26,7 @@ .umb-multiple-textbox .textbox-wrapper i.handle { margin-left: 5px; + cursor: move; } .umb-multiple-textbox .textbox-wrapper a.remove { @@ -37,6 +38,10 @@ &:extend(.umb-node-preview-add); } +.umb-multiple-textbox .add-link:hover { + &:extend(.umb-node-preview-add:hover); +} + .umb-editor-wrapper .umb-multiple-textbox .add-link { &:extend(.umb-editor-wrapper .umb-node-preview); } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 631ef5c2bd..e0abd3fd26 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -62,6 +62,7 @@ -khtml-user-select: none; -webkit-user-select: none; -o-user-select: none; + user-select: none; } .umb-nested-content__heading { 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 45170c9602..daee29351c 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 @@ -261,6 +261,7 @@ .umb-package-details { display: flex; + flex-flow: row wrap; } a.umb-package-details__back-link { @@ -280,6 +281,7 @@ a.umb-package-details__back-link { flex: 1 1 auto; margin-right: 20px; width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling + min-width: 500px; } .umb-package-details__sidebar { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-panel-group.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-panel-group.less new file mode 100644 index 0000000000..487c1881b1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-panel-group.less @@ -0,0 +1,111 @@ +.umb-panel-group__details { + border-radius: 3px; + margin-bottom: 40px; +} + +.umb-panel-group__details-group { + background: @white; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + border-radius: @baseBorderRadius; +} + +.umb-panel-group__details-group-title { + background-color: @blueExtraDark; + padding: 10px 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 3px 3px 0 0; +} + +.umb-panel-group__details-group-name { + font-size: 16px; + color: @white; + font-weight: bold; +} + +.umb-panel-group__details-checks { + border-top: none; + border-radius: 0 0 3px 3px; + background: @white; +} + +.umb-panel-group__details-check { + position: relative; +} + +.umb-panel-group__details-check-title { + padding: 15px 20px; +} + +.umb-panel-group__details-check-name { + font-size: 15px; + color: @black; + font-weight: bold; + margin-bottom: 3px; +} + +.umb-panel-group__details-check-description { + font-size: 13px; + color: @gray-3; + line-height: 1.6em; +} + +.umb-panel-group__details-status { + padding: 15px 0; + display: flex; + border-top: 2px solid @gray-10; +} + +.umb-panel-group__details-status-overlay { + background: @white; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + opacity: 0.9; +} + +.umb-panel-group__details-status:last-child { + border-bottom: none; +} + +.umb-panel-group__details-status-icon-container { + flex: 0 0 50px; + display: flex; + justify-content: center; + padding: 0 20px; +} + +.umb-panel-group__details-status-content { + padding: 0 20px; + flex: 1 1 auto; +} + +.umb-panel-group__details-status-text { + line-height: 1.6em; +} + +.umb-panel-group__details-status-actions { + display: flex; + flex-direction: column; + margin-top: 10px; +} + +.umb-panel-group__details-status-action { + background-color: @gray-10; + padding: 10px; + margin-bottom: 10px; + border-radius: 3px; +} + +.umb-panel-group__details-status-action:last-child { + margin-bottom: 0; +} + +.umb-panel-group__details-status-action-description { + margin-top: 5px; + font-size: 12px; + padding-left: 165px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less index 5e98075fc2..03816637a7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less @@ -4,10 +4,6 @@ .umb-progress-circle__view-box { position: absolute; - -webkit-transform: rotate(-90deg); - -moz-transform: rotate(-90deg); - -o-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); transform: rotate(-90deg); } 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 291eef43e0..a0c3f02e40 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 @@ -151,14 +151,14 @@ input.umb-table__input { // Show checkmark when checked, hide file icon .umb-table-row--selected { - /* + .umb-table-body__fileicon { display: none; } .umb-table-body__checkicon { display: inline-block; } - */ + &::before { content: ""; position: absolute; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index de2dca5f91..e24f68078b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -8,6 +8,8 @@ box-sizing: border-box; max-width: 100%; display: flex; + position: relative; + user-select: none; } .umb-user-card:hover, @@ -15,6 +17,22 @@ outline: none; text-decoration: none !important; } +.umb-user-card.-selected { + &::before { + content: ""; + position: absolute; + z-index:2; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid @ui-selected-border; + border-radius: 5px; + box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); + pointer-events: none; + } + +} .umb-user-card__content { position: relative; @@ -30,9 +48,12 @@ max-width: 100%; } -.umb-user-card__content:hover, -.umb-user-card:focus .umb-user-card__content { - border-color: @turquoise; +.umb-user-card__goToUser { + &:hover { + .umb-user-card__name { + text-decoration: underline; + } + } } .umb-user-card__avatar { @@ -47,24 +68,13 @@ left: 10px; } + .umb-user-card__name { font-size: 15px; font-weight: bold; text-align: center; margin-bottom: 2px; - word-wrap: break-word; -} - -.umb-user-card__checkmark { - position: absolute; - top: 10px; - right: 10px; - display: none; -} - -.umb-user-card:hover .umb-user-card__checkmark, -.umb-user-card__checkmark--visible { - display: block; + word-wrap: break-word; } .umb-user-card__group { @@ -77,4 +87,4 @@ font-size: 13px; text-align: center; margin-top: auto; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards.less b/src/Umbraco.Web.UI.Client/src/less/dashboards.less deleted file mode 100644 index 7bf38e1583..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards.less +++ /dev/null @@ -1,136 +0,0 @@ -.umb-dashboards-forms-install { - background: url('../img/forms/installer-background.png'); - background-repeat: repeat-x; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - padding-top: 30px; - background-color: @white; - overflow: auto; - - small { - font-size: 14px; - opacity: .5; - } - - .umb-loader { - width: 640px; - height: 4px; - } - - .video_player{ - video { - width: 100%; - max-width: 640px; - border: 1px solid @gray-9; - border-left: none; - border-bottom: none; - -moz-box-sizing:border-box; - box-sizing:border-box; - } - - input[type="range"] { - position: relative; - z-index: 999; - max-width: 640px; - width: 100%; - margin: 0 auto; - opacity: 0; - cursor: pointer; - } - - input[type="range"]::-ms-fill-lower, input[type="range"]::-ms-fill-upper { - background: transparent; - } - - input[type="range"]::-ms-tooltip { - display: none; - } - - .video-controls { - position: relative; - max-width: 640px; - height: 20px; - margin: -13px auto 40px; - opacity: .8; - } - - .loader { - display: block; - width: 100%; - height: 3px; - margin-top: -13px; - background-color: @gray-10; - } - - .progress-bar { - display: block; - -moz-box-sizing:border-box; - box-sizing: border-box; - max-width: 100%; - width: 200px; - height: 100%; - background: @gray-5; - } - - .video-controls, .loader, .progress-bar { - -webkit-transition: all 150ms ease-in-out; - transition: all 150ms ease-in-out; - } - - .progress-bar { - transition-property: background; - } - - .video_player video:hover + .video-controls, .video-controls:hover { - margin-top: -19px; - margin-bottom: 46px; - opacity: 1; - } - - .video_player video:hover + .video-controls .loader, .video-controls:hover .loader { - height: 8px; - } - - .video_player video:hover + .video-controls .progress-bar, .video-controls:hover .progress-bar { - background: @blueMid; - } - } - - .forms-install-button { - .btn { - padding: 14px 40px; - } - } - - .installer-top { - overflow: hidden; - margin: 0 auto 30px; - height: 80px; - line-height: 80px; - text-align: center; - position: relative; - display: flex; - justify-content: center; - - .icon, h3 { - display: inline-block; - } - - .icon { - font-size: 80px; - } - - h3 { - margin: 0 0 0 20px; - line-height: 80px; - font-weight: 700; - font-size: 36px; - letter-spacing: -1px; - } - } - - -} diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less new file mode 100644 index 0000000000..0c219af1e4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less @@ -0,0 +1,15 @@ +.examine-management { + + .form-search { + + .search-query { + flex: 1 1 auto; + width: auto; + } + + button.btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/getstarted.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less similarity index 100% rename from src/Umbraco.Web.UI.Client/src/less/getstarted.less rename to src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/healthcheck.less new file mode 100644 index 0000000000..449cc4066b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/healthcheck.less @@ -0,0 +1,134 @@ + +.umb-healthcheck { + display: flex; + flex-wrap: wrap; + margin-left: -10px; + margin-right: -10px; +} + +.umb-healthcheck-help-text { + line-height: 1.6em; + max-width: 750px; +} + +.umb-healthcheck-action-bar { + display: flex; + justify-content: flex-end; + margin-bottom: 20px; +} + + +/* Group and states */ +.umb-healthcheck-group { + display: flex; + flex-wrap: wrap; + flex-direction: column; + background: @white; + border-radius: 3px; + padding: 20px; + box-sizing: border-box; + text-align: center; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + height: 100%; + box-sizing: border-box; +} + +.umb-healthcheck-group:hover { + box-shadow: 0 3px 6px 0 rgba(0,0,0,0.16); + cursor: pointer; + transition: box-shadow 150ms ease-in-out; +} + +.umb-healthcheck-group__load-container { + position: relative; + height: 30px; + margin-top: 15px; + margin-bottom: 16px; +} + + +/* Title */ +.umb-healthcheck-title { + font-size: 14px; + font-weight: bold; +} + + +/* Messages */ +.umb-healthcheck-messages { + margin-top: 15px; +} + +.umb-healthcheck-message { + position: relative; + background: @gray-10; + border-radius: 50px; + display: inline-flex; + align-items: center; + padding-left: 8px; + padding-right: 8px; + margin-bottom: 5px; + color: @black; + font-weight: bold; + font-size: 13px; +} + +.umb-healthcheck-message i { + font-size: 15px; + margin-right: 3px; +} + +.umb-healthcheck-details-link { + color: @turquoise-d1; +} + +.umb-healthcheck-details-link:hover { + text-decoration: none; + color: @turquoise-d1; +} + + +/* Helpers */ +.align-self-center { + align-self: center; +} + + +/* Spacing for boxes */ +.umb-air { + flex: 0 0 auto; + flex-basis: 100%; + max-width: 100%; + padding: 10px; + box-sizing: border-box; + + @media (min-width: 500px) { + flex-basis: 50%; + max-width: 50%; + } + + @media (min-width: 768px) { + flex-basis: 20%; + max-width: 20%; + } +} + + +/* DETAILS */ + +.umb-healthcheck-back-link { + font-weight: bold; + color: @black; +} + +.umb-healthcheck-status-icon { + font-size: 20px; + margin-top: 2px; +} + +.umb-healthcheck-status-icon.-large { + width: 70px; + height: 70px; + font-size: 30px; + background-color: @white; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less new file mode 100644 index 0000000000..b51bfeffa9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less @@ -0,0 +1,150 @@ +.umb-dashboards-forms-install { + background: url('../img/forms/installer-background.png'); + background-repeat: repeat-x; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding-top: 30px; + background-color: @white; + overflow: auto; + display: flex; + + small { + font-size: 14px; + opacity: .5; + } + + .umb-loader { + width: 640px; + height: 4px; + } + + .video_player { + video { + width: 100%; + max-width: 640px; + border: 1px solid @gray-9; + border-left: none; + border-bottom: none; + box-sizing: border-box; + } + + input[type="range"] { + position: relative; + z-index: 999; + max-width: 640px; + width: 100%; + margin: 0 auto; + opacity: 0; + cursor: pointer; + } + + input[type="range"]::-ms-fill-lower, input[type="range"]::-ms-fill-upper { + background: transparent; + } + + input[type="range"]::-ms-tooltip { + display: none; + } + + .video-controls { + position: relative; + max-width: 640px; + height: 20px; + margin: -13px auto 40px; + opacity: .8; + } + + .loader { + display: block; + width: 100%; + height: 3px; + margin-top: -13px; + background-color: @gray-10; + } + + .progress-bar { + display: block; + box-sizing: border-box; + max-width: 100%; + width: 200px; + height: 100%; + background: @gray-5; + } + + .video-controls, .loader, .progress-bar { + transition: all 150ms ease-in-out; + } + + .progress-bar { + transition-property: background; + } + + .video_player video:hover + .video-controls, .video-controls:hover { + margin-top: -19px; + margin-bottom: 46px; + opacity: 1; + } + + .video_player video:hover + .video-controls .loader, .video-controls:hover .loader { + height: 8px; + } + + .video_player video:hover + .video-controls .progress-bar, .video-controls:hover .progress-bar { + background: @blueMid; + } + } + + .forms-install-button { + .btn { + padding: 14px 40px; + } + } + + .step-text { + font-size: 16px; + line-height: 1.5; + color: #4c4c4c; + margin-bottom: 20px; + } + + .installing { + position: relative; + } + + .installer-wrapper { + max-width: 800px; + text-align: center; + margin: auto; + padding: 20px; + } + + .installer-intro { + overflow: hidden; + margin: 0 auto 30px; + height: 80px; + line-height: 80px; + text-align: center; + position: relative; + display: flex; + justify-content: center; + + .icon, h3 { + display: inline-block; + } + + .icon { + font-size: 80px; + } + + h3 { + margin: 0 0 0 20px; + line-height: 80px; + font-weight: 700; + font-size: 36px; + letter-spacing: -1px; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/fonts.less b/src/Umbraco.Web.UI.Client/src/less/fonts.less index 613794d1bf..73bc36037f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/fonts.less +++ b/src/Umbraco.Web.UI.Client/src/less/fonts.less @@ -7,15 +7,18 @@ Black: font-weight: 900; */ +@fontPath: '../fonts'; +@latoPath: '@{fontPath}/lato'; + /* Webfont: LatoLatin-Black */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Black.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Black.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Black.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Black.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Black.ttf') format('truetype'); + src: local('LatoLatin Black'), + local('LatoLatin-Black'), + url('@{latoPath}/LatoLatin-Black.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-Black.woff') format('woff'); /* Modern Browsers */ font-style: normal; + font-display: swap; font-weight: 900; text-rendering: optimizeLegibility; } @@ -23,123 +26,90 @@ /* Webfont: LatoLatin-BlackItalic */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-BlackItalic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-BlackItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-BlackItalic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BlackItalic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BlackItalic.ttf') format('truetype'); + src: local('LatoLatin BlackItalic'), + local('LatoLatin-BlackItalic'), + url('@{latoPath}/LatoLatin-BlackItalic.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-BlackItalic.woff') format('woff'); /* Modern Browsers */ font-style: italic; font-weight: 900; + font-display: swap; text-rendering: optimizeLegibility; } /* Webfont: LatoLatin-Bold */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Bold.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Bold.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Bold.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Bold.ttf') format('truetype'); + src: local('LatoLatin Bold'), + local('LatoLatin-Bold'), + url('@{latoPath}/LatoLatin-Bold.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-Bold.woff') format('woff'); /* Modern Browsers */ font-style: normal; - font-weight: bold; + font-weight: 700; + font-display: swap; text-rendering: optimizeLegibility; } /* Webfont: LatoLatin-BoldItalic */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-BoldItalic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-BoldItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-BoldItalic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BoldItalic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BoldItalic.ttf') format('truetype'); + src: local('LatoLatin BoldItalic'), + local('LatoLatin-BoldItalic'), + url('@{latoPath}/LatoLatin-BoldItalic.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-BoldItalic.woff') format('woff'); /* Modern Browsers */ font-style: italic; - font-weight: bold; + font-weight: 700; + font-display: swap; text-rendering: optimizeLegibility; } /* Webfont: LatoLatin-Italic */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Italic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Italic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Italic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Italic.ttf') format('truetype'); + src: local('LatoLatin Italic'), + local('LatoLatin-Italic'), + url('@{latoPath}/LatoLatin-Italic.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-Italic.woff') format('woff'); /* Modern Browsers */ font-style: italic; - font-weight: normal; + font-weight: 400; + font-display: swap; text-rendering: optimizeLegibility; } /* Webfont: LatoLatin-Regular */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Regular.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Regular.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Regular.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Regular.ttf') format('truetype'); + src: local('LatoLatin Regular'), + local('LatoLatin-Regular'), + url('@{latoPath}/LatoLatin-Regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-Regular.woff') format('woff'); /* Modern Browsers */ font-style: normal; - font-weight: normal; + font-weight: 400; + font-display: swap; text-rendering: optimizeLegibility; } /* Webfont: LatoLatin-Light */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Light.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Light.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Light.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Light.ttf') format('truetype'); + src: local('LatoLatin Light'), + local('LatoLatin-Light'), + url('@{latoPath}/LatoLatin-Light.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-Light.woff') format('woff'); /* Modern Browsers */ font-style: normal; font-weight: 300; + font-display: swap; text-rendering: optimizeLegibility; } /* Webfont: LatoLatin-LightItalic */ @font-face { font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-LightItalic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-LightItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-LightItalic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-LightItalic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-LightItalic.ttf') format('truetype'); + src: local('LatoLatin LightItalic'), + local('LatoLatin-LightItalic'), + url('@{latoPath}/LatoLatin-LightItalic.woff2') format('woff2'), /* Super Modern Browsers */ + url('@{latoPath}/LatoLatin-LightItalic.woff') format('woff'); /* Modern Browsers */ font-style: italic; font-weight: 300; + font-display: swap; text-rendering: optimizeLegibility; } - - -/* - Open Sans - - Normal: font-weight: 400; - Semi-bold: font-weight: 600; - -*/ - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans/OpenSans-Regular-webfont.eot'); - src: local('Open Sans'), local('OpenSans'), - url('../fonts/opensans/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/opensans/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); - font-weight: 400; - font-style: normal; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans/OpenSans-Semibold-webfont.eot'); - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), - url('../fonts/opensans/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans/OpenSans-Semibold-webfont.ttf') format('truetype'), - url('../fonts/opensans/OpenSans-Semibold-webfont.svg#open_sanssemibold') format('svg'); - font-weight: 600; - font-style: normal; -} - - diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 7b2afad20b..45188ca02e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -258,7 +258,6 @@ input[type="color"], input[type="radio"], input[type="checkbox"] { margin: 4px 0 0; - *margin-top: 0; /* IE7 */ margin-top: 1px \9; /* IE8-9 */ line-height: normal; } @@ -277,8 +276,7 @@ input[type="checkbox"] { // Set the height of select and file controls to match text inputs select, input[type="file"] { - height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ - *margin-top: 4px; /* For IE7, add top margin to align select with labels */ + height: @inputHeight; line-height: @inputHeight; } @@ -526,7 +524,6 @@ input[type="checkbox"][readonly] { .help-inline { display: inline-block; - .ie7-inline-block(); vertical-align: middle; padding-left: 5px; } @@ -574,14 +571,19 @@ div.help { width: auto; height: 22px; min-width: 18px; - padding: 4px 6px; + padding: 5px 6px 3px 6px; font-size: @baseFontSize; font-weight: normal; line-height: @baseLineHeight; text-align: center; - text-shadow: 0 1px 0 @white; - background-color: @gray-10; - border: 1px solid @purple-l3; + //text-shadow: 0 1px 0 @white; + background-color: @white; + border: 1px solid @inputBorder; + color: @ui-option-type; + &:hover { + border-color:@inputBorderFocus; + color: @ui-option-type-hover; + } } .add-on, .btn, @@ -644,7 +646,6 @@ div.help { input.search-query { padding-right: 4px \9; padding-left: 14px; - padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ margin: 0; // Remove the default margin on all inputs } @@ -679,7 +680,6 @@ input.search-query { .input-prepend, .input-append { display: inline-block; - .ie7-inline-block(); margin-bottom: 0; vertical-align: top; } @@ -755,12 +755,7 @@ legend + .control-group { } // Move over all input controls and content .controls { - // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend - // don't inherit the margin of the parent, in this case .controls - *display: inline-block; - *padding-left: 20px; margin-left: @horizontalComponentOffset; - *margin-left: 0; &:first-child { *padding-left: @horizontalComponentOffset; } diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index 469ab2becf..bb684cd69b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -21,9 +21,7 @@ overflow: hidden; padding: 5px; border-radius:5px; - -webkit-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - -moz-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); } .usky-grid .ui-sortable-helper *{ @@ -91,8 +89,6 @@ .usky-grid .td.last { border-right:1px dashed rgba(182, 182, 182, 0.0) !important; } - .usky-grid .tr { - } .usky-grid .middle { text-align: center; @@ -106,17 +102,6 @@ } - - // COLUMN - // ------------------------- - .usky-grid .usky-column { - - } - .usky-grid .usky-column.last { - - } - - // ROW // ------------------------- .usky-grid .usky-row{ @@ -128,12 +113,6 @@ border-bottom:1px dashed rgba(182, 182, 182, 0) !important; } -/* -.usky-grid .selectedRow { - border: 1px solid rgba(182, 182, 182, 0.3); -}*/ - - // CELL @@ -146,8 +125,6 @@ .usky-grid .cell-tools { transition: all .20s ease-in-out; - -moz-transition: all .20s ease-in-out; - -webkit-transition: all .20s ease-in-out; position: absolute; bottom: 0; top: 0; @@ -217,33 +194,13 @@ // CONTROL // ------------------------- .usky-grid .usky-control { - /*transition: all .20s ease-in-out; - -moz-transition: all .20s ease-in-out; - -webkit-transition: all .20s ease-in-out;*/ position:relative; display:block; - - /* - border: 1px dashed rgba(255, 0, 0, .0); - border-bottom-width: 1px; - */ - - -webkit-background-clip: padding-box; /* for Safari */ - background-clip: padding-box; /* for IE9+, Firefox 4+, Opera, Chrome */ + background-clip: padding-box; margin: 10px 0 0 0; } -.usky-grid .usky-control:hover{ - /*border: 1px solid rgba(182, 182, 182, 0.3);*/ - /*border-bottom: 1px solid rgba(182, 182, 182, 0.3);*/ -} - -.usky-grid .usky-control-placeholder:hover{ - /*border: 1px solid rgba(182, 182, 182, 0);*/ - /*border-bottom:1px solid rgba(182, 182, 182, 0.0) !important;*/ -} - .usky-grid .warnhighlight, .usky-grid .td.last.warnhighlight{ border: 1px dashed @red !important; } @@ -395,10 +352,10 @@ } .usky-grid .iconBox:hover, .usky-grid .iconBox:hover *{ - background: @blueMid !important; - color: @white !important; - border-color: @blueMid !important; - text-decoration:none; + background: @blueMid !important; + color: @white !important; + border-color: @blueMid !important; + text-decoration:none; } .usky-grid .iconBox a:hover { @@ -408,21 +365,14 @@ .usky-grid .iconBox.selected { -webkit-appearance: none; - background-image: -moz-linear-gradient(top,#e6e6e6,#bfbfbf); background-image: -webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf)); background-image: -webkit-linear-gradient(top,#e6e6e6,#bfbfbf); - background-image: -o-linear-gradient(top,#e6e6e6,#bfbfbf); background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0); zoom: 1; border-color: #bfbfbf #bfbfbf #999; border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); - -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); - -webkit-border-radius: 3px; - -moz-border-radius: 3px; border-radius: 3px; background: transparent; } @@ -482,8 +432,6 @@ padding: -1px; position: absolute; margin: -1px -1px 0 -1px; - -webkit-box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); - -moz-box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); z-index: 9999999; } @@ -651,8 +599,6 @@ display: inline-block; position: relative; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; width: 125px; margin: 35px 40px 15px 0; @@ -719,8 +665,6 @@ display: block; float: left; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; width: 33.3%; /* temp value */ height: 10px; @@ -772,9 +716,7 @@ top: 0; left: 50%; z-index: 6660; - -webkit-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - -moz-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); } diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 0bbb89a250..726b3fa5ed 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -59,7 +59,6 @@ iframe, .content-column-body { right: 0; margin: 0; opacity: 0; - filter: alpha(opacity=0); transform: translate(-300px, 0) scale(4); font-size: 23px; direction: ltr; diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less deleted file mode 100644 index 23bbbf4931..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less +++ /dev/null @@ -1,246 +0,0 @@ - -.umb-healthcheck { - display: flex; - flex-wrap: wrap; - margin-left: -10px; - margin-right: -10px; -} - -.umb-healthcheck-help-text { - line-height: 1.6em; - max-width: 750px; -} - -.umb-healthcheck-action-bar { - display: flex; - justify-content: flex-end; - margin-bottom: 20px; -} - - -/* Group and states */ -.umb-healthcheck-group { - display: flex; - flex-wrap: wrap; - flex-direction: column; - background: @white; - border-radius: 3px; - padding: 20px; - box-sizing: border-box; - text-align: center; - box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); - height: 100%; - box-sizing: border-box; -} - -.umb-healthcheck-group:hover { - box-shadow: 0 3px 6px 0 rgba(0,0,0,0.16); - cursor: pointer; - transition: box-shadow 150ms ease-in-out; -} - -.umb-healthcheck-group__load-container { - position: relative; - height: 30px; - margin-top: 15px; - margin-bottom: 16px; -} - - -/* Title */ -.umb-healthcheck-title { - font-size: 14px; - font-weight: bold; -} - - -/* Messages */ -.umb-healthcheck-messages { - margin-top: 15px; -} - -.umb-healthcheck-message { - position: relative; - background: @gray-10; - border-radius: 50px; - display: inline-flex; - align-items: center; - padding-left: 8px; - padding-right: 8px; - margin-bottom: 5px; - color: @black; - font-weight: bold; - font-size: 13px; -} - -.umb-healthcheck-message i { - font-size: 15px; - margin-right: 3px; -} - -.umb-healthcheck-details-link { - color: @turquoise-d1; -} - -.umb-healthcheck-details-link:hover { - text-decoration: none; - color: @turquoise-d1; -} - - -/* Helpers */ -.align-self-center { - align-self: center; -} - - -/* Spacing for boxes */ -.umb-air { - flex: 0 0 auto; - flex-basis: 100%; - max-width: 100%; - padding: 10px; - box-sizing: border-box; - - @media (min-width: 500px) { - flex-basis: 50%; - max-width: 50%; - } - - @media (min-width: 768px) { - flex-basis: 20%; - max-width: 20%; - } -} - - -/* DETAILS */ - -.umb-healthcheck-back-link { - font-weight: bold; - color: @black; -} - -.umb-healthcheck-group__details { - border-radius: 3px; - margin-bottom: 40px; -} - -.umb-healthcheck-group__details-group { - background: @white; - box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); - border-radius: @baseBorderRadius; -} - -.umb-healthcheck-group__details-group-title { - background-color: @blueExtraDark; - padding: 10px 20px; - display: flex; - align-items: center; - justify-content: space-between; - border-radius: 3px 3px 0 0; -} - -.umb-healthcheck-group__details-group-name { - font-size: 16px; - color: @white; - font-weight: bold; -} - -.umb-healthcheck-group__details-checks { - border-top: none; - border-radius: 0 0 3px 3px; - background: @white; -} - -.umb-healthcheck-group__details-check { - position: relative; -} - -.umb-healthcheck-group__details-check-title { - padding: 15px 20px; -} - -.umb-healthcheck-group__details-check-name { - font-size: 15px; - color: @black; - font-weight: bold; - margin-bottom: 3px; -} - -.umb-healthcheck-group__details-check-description { - font-size: 13px; - color: @gray-3; - line-height: 1.6em; -} - -.umb-healthcheck-group__details-status { - padding: 15px 0; - display: flex; - border-top: 2px solid @gray-10; -} - -.umb-healthcheck-group__details-status-overlay { - background: @white; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - opacity: 0.9; -} - -.umb-healthcheck-group__details-status:last-child { - border-bottom: none; -} - -.umb-healthcheck-group__details-status-icon-container { - flex: 0 0 50px; - display: flex; - justify-content: center; - padding: 0 20px; -} - -.umb-healthcheck-status-icon { - font-size: 20px; - margin-top: 2px; -} - -.umb-healthcheck-status-icon.-large { - width: 70px; - height: 70px; - font-size: 30px; - background-color: @white; -} - -.umb-healthcheck-group__details-status-content { - padding: 0 20px; - flex: 1 1 auto; -} - -.umb-healthcheck-group__details-status-text { - line-height: 1.6em; -} - -.umb-healthcheck-group__details-status-actions { - display: flex; - flex-direction: column; - margin-top: 10px; -} - -.umb-healthcheck-group__details-status-action { - background-color: @gray-10; - padding: 10px; - margin-bottom: 10px; - border-radius: 3px; -} - -.umb-healthcheck-group__details-status-action:last-child { - margin-bottom: 0; -} - -.umb-healthcheck-group__details-status-action-description { - margin-top: 5px; - font-size: 12px; - padding-left: 165px; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/helveticons.less b/src/Umbraco.Web.UI.Client/src/less/helveticons.less index 1083a490e0..82663270e9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/helveticons.less +++ b/src/Umbraco.Web.UI.Client/src/less/helveticons.less @@ -1875,3 +1875,6 @@ i.small{ .icon-alert-alt:before { content: "\e25d"; } +.icon-section:before { + content: "\e24f"; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 485d20f9c3..798252c394 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -21,9 +21,6 @@ html { background: url('../img/installer.jpg') no-repeat center center fixed; - -webkit-background-size: cover; - -moz-background-size: cover; - -o-background-size: cover; background-size: cover; } @@ -153,9 +150,6 @@ input.ng-dirty.ng-invalid{border-color: #b94a48; color: #b94a48;} } .fade-hide, .fade-show { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; } .fade-hide { @@ -173,84 +167,23 @@ input.ng-dirty.ng-invalid{border-color: #b94a48; color: #b94a48;} .umb-loader{ -background-color: @white; -margin-top:0; -margin-left:-100%; --moz-animation-name:bounce_loadingProgressG; --moz-animation-duration:1s; --moz-animation-iteration-count:infinite; --moz-animation-timing-function:linear; --webkit-animation-name:bounce_loadingProgressG; --webkit-animation-duration:1s; --webkit-animation-iteration-count:infinite; --webkit-animation-timing-function:linear; --ms-animation-name:bounce_loadingProgressG; --ms-animation-duration:1s; --ms-animation-iteration-count:infinite; --ms-animation-timing-function:linear; --o-animation-name:bounce_loadingProgressG; --o-animation-duration:1s; --o-animation-iteration-count:infinite; --o-animationtiming-function:linear; -animation-name:bounce_loadingProgressG; -animation-duration:1s; -animation-iteration-count:infinite; -animation-timing-function:linear; -width:100%; -height: 5px; + background-color: @white; + margin-top:0; + margin-left:-100%; + animation-name:bounce_loadingProgressG; + animation-duration:1s; + animation-iteration-count:infinite; + animation-timing-function:linear; + width:100%; + height: 5px; } - - @-moz-keyframes bounce_loadingProgressG{ +@keyframes bounce_loadingProgressG{ 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-webkit-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-ms-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-o-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; + margin-left:-100%; } 100%{ - margin-left:100%; + margin-left:100%; } } @@ -317,4 +250,4 @@ select { #starterKits a.thumbnail img { position:relative; z-index:100; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index c0cb609afb..8b5a295752 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -54,9 +54,6 @@ .umb-listview table input[type="text"] { background: none; - -webkit-transition: all .5s; - -moz-transition: all .5s; - -o-transition: all .5s; transition: all .5s; width: 60px; padding: 4px 0 4px 20px; @@ -181,10 +178,6 @@ /* ---------- LAYOUTS ---------- */ -.list-view-layouts { - -} - .list-view-layout { display: flex; align-items: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index bf30b26777..62ddb28041 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -98,10 +98,6 @@ h5.-black { display: block; color: @gray-8; text-decoration: none; - -webkit-transition: all 0.3s ease-in-out; - -moz-transition: all 0.3s ease-in-out; - -ms-transition: all 0.3s ease-in-out; - -o-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out; } @@ -379,8 +375,6 @@ table thead a { height: 100%; z-index: 5999; opacity: .6; - filter: alpha(opacity=6); - -webkit-box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,0.2),inset 0 0 20px 41px rgba(0,0,0,0.2); box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,0.2),inset 0 0 20px 41px rgba(0,0,0,0.2); } @@ -495,95 +489,32 @@ table thead a { // ------------------------ .umb-loader{ -background-color: @blue; -margin-top:0; -margin-left:-100%; --moz-animation-name:bounce_loadingProgressG; --moz-animation-duration:1s; --moz-animation-iteration-count:infinite; --moz-animation-timing-function:linear; --webkit-animation-name:bounce_loadingProgressG; --webkit-animation-duration:1s; --webkit-animation-iteration-count:infinite; --webkit-animation-timing-function:linear; --ms-animation-name:bounce_loadingProgressG; --ms-animation-duration:1s; --ms-animation-iteration-count:infinite; --ms-animation-timing-function:linear; --o-animation-name:bounce_loadingProgressG; --o-animation-duration:1s; --o-animation-iteration-count:infinite; --o-animationtiming-function:linear; -animation-name:bounce_loadingProgressG; -animation-duration:1s; -animation-iteration-count:infinite; -animation-timing-function:linear; -width:100%; -height:2px; + background-color: @blue; + margin-top:0; + margin-left:-100%; + animation-name:bounce_loadingProgressG; + animation-duration:1s; + animation-iteration-count:infinite; + animation-timing-function:linear; + width:100%; + height:2px; } - - @-moz-keyframes bounce_loadingProgressG{ +@keyframes bounce_loadingProgressG{ 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-webkit-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-ms-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-o-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; + margin-left:-100%; } 100%{ - margin-left:100%; + margin-left:100%; } } .umb-loader-wrapper { - position: absolute; right: 0; left: 0; margin: 10px 0; overflow: hidden; - } .umb-loader-wrapper.-bottom { @@ -663,7 +594,6 @@ input[type=checkbox]:checked + .input-label--small { .visuallyhidden{ position: absolute !important; - clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ clip: rect(1px, 1px, 1px, 1px); padding:0 !important; border:0 !important; diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index b67fd1dd25..20d4e8d1f8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -10,7 +10,6 @@ // -------- // For clearing floats like a boss h5bp.com/q .clearfix { - *zoom: 1; &:before, &:after { display: table; @@ -42,31 +41,6 @@ margin-right: auto; } -// IE7 inline-block -// ---------------- -.ie7-inline-block() { - *display: inline; /* IE7 inline-block hack */ - *zoom: 1; -} - -// IE7 likes to collapse whitespace on either side of the inline-block elements. -// Ems because we're attempting to match the width of a space character. Left -// version is for form buttons, which typically come after other elements, and -// right version is for icons, which come before. Applying both is ok, but it will -// mean that space between those elements will be .6em (~2 space characters) in IE7, -// instead of the 1 space in other browsers. -.ie7-restore-left-whitespace() { - *margin-left: .3em; - - &:first-child { - *margin-left: 0; - } -} - -.ie7-restore-right-whitespace() { - *margin-right: .3em; -} - // Sizing shortcuts // ------------------------- .size(@height, @width) { @@ -190,18 +164,6 @@ background-color: @backgroundColor; border-color: @textColor; } - //SD: We could do this but need to get the colors right - /*input.ng-invalid { - &:-moz-placeholder { - color: lighten(@textColor, 50%) !important; - } - &:-ms-input-placeholder { - color: lighten(@textColor, 50%) !important; - } - &::-webkit-input-placeholder { - color: lighten(@textColor, 50%) !important; - } - }*/ } // CSS3 PROPERTIES @@ -209,31 +171,21 @@ // Border Radius .border-radius(@radius) { - -webkit-border-radius: @radius; - -moz-border-radius: @radius; - border-radius: @radius; + border-radius: @radius; } // Single Corner Border Radius .border-top-left-radius(@radius) { - -webkit-border-top-left-radius: @radius; - -moz-border-radius-topleft: @radius; - border-top-left-radius: @radius; + border-top-left-radius: @radius; } .border-top-right-radius(@radius) { - -webkit-border-top-right-radius: @radius; - -moz-border-radius-topright: @radius; - border-top-right-radius: @radius; + border-top-right-radius: @radius; } .border-bottom-right-radius(@radius) { - -webkit-border-bottom-right-radius: @radius; - -moz-border-radius-bottomright: @radius; - border-bottom-right-radius: @radius; + border-bottom-right-radius: @radius; } .border-bottom-left-radius(@radius) { - -webkit-border-bottom-left-radius: @radius; - -moz-border-radius-bottomleft: @radius; - border-bottom-left-radius: @radius; + border-bottom-left-radius: @radius; } // Single Side Border Radius @@ -256,66 +208,36 @@ // Drop shadows .box-shadow(@shadow) { - -webkit-box-shadow: @shadow; - -moz-box-shadow: @shadow; - box-shadow: @shadow; + box-shadow: @shadow; } // Transitions .transition(@transition) { - -webkit-transition: @transition; - -moz-transition: @transition; - -o-transition: @transition; - transition: @transition; + transition: @transition; } .transition-delay(@transition-delay) { - -webkit-transition-delay: @transition-delay; - -moz-transition-delay: @transition-delay; - -o-transition-delay: @transition-delay; - transition-delay: @transition-delay; + transition-delay: @transition-delay; } .transition-duration(@transition-duration) { - -webkit-transition-duration: @transition-duration; - -moz-transition-duration: @transition-duration; - -o-transition-duration: @transition-duration; - transition-duration: @transition-duration; + transition-duration: @transition-duration; } // Transformations .rotate(@degrees) { - -webkit-transform: rotate(@degrees); - -moz-transform: rotate(@degrees); - -ms-transform: rotate(@degrees); - -o-transform: rotate(@degrees); - transform: rotate(@degrees); + transform: rotate(@degrees); } .scale(@ratio) { - -webkit-transform: scale(@ratio); - -moz-transform: scale(@ratio); - -ms-transform: scale(@ratio); - -o-transform: scale(@ratio); - transform: scale(@ratio); + transform: scale(@ratio); } .translate(@x, @y) { - -webkit-transform: translate(@x, @y); - -moz-transform: translate(@x, @y); - -ms-transform: translate(@x, @y); - -o-transform: translate(@x, @y); - transform: translate(@x, @y); + transform: translate(@x, @y); } .skew(@x, @y) { - -webkit-transform: skew(@x, @y); - -moz-transform: skew(@x, @y); - -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 - -o-transform: skew(@x, @y); - transform: skew(@x, @y); - -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 + transform: skew(@x, @y); + -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 } .translate3d(@x, @y, @z) { - -webkit-transform: translate3d(@x, @y, @z); - -moz-transform: translate3d(@x, @y, @z); - -o-transform: translate3d(@x, @y, @z); - transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); } // Backface visibility @@ -331,25 +253,18 @@ // Background clipping // Heads up: FF 3.6 and under need "padding" instead of "padding-box" .background-clip(@clip) { - -webkit-background-clip: @clip; - -moz-background-clip: @clip; - background-clip: @clip; + background-clip: @clip; } // Background sizing .background-size(@size) { - -webkit-background-size: @size; - -moz-background-size: @size; - -o-background-size: @size; - background-size: @size; + background-size: @size; } // Box sizing .box-sizing(@boxmodel) { - -webkit-box-sizing: @boxmodel; - -moz-box-sizing: @boxmodel; - box-sizing: @boxmodel; + box-sizing: @boxmodel; } // User select @@ -391,7 +306,6 @@ // Opacity .opacity(@opacity) { opacity: @opacity / 100; - filter: ~"alpha(opacity=@{opacity})"; } @@ -428,7 +342,6 @@ background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down } .vertical(@startColor: #555, @endColor: #333) { background-color: mix(@startColor, @endColor, 60%); @@ -438,7 +351,6 @@ background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down } .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { background-color: @endColor; @@ -456,7 +368,6 @@ background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback } .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { @@ -467,7 +378,6 @@ background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback } .radial(@innerColor: #555, @outerColor: #333) { background-color: @outerColor; @@ -486,10 +396,6 @@ background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); } } -// Reset filters for IE -.reset-filter() { - filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); -} @@ -500,14 +406,8 @@ // ------------------------- // Dividers (basically an hr) within dropdowns and nav lists .nav-divider(@top: @gray-8, @bottom: @white) { - // IE7 needs a set width since we gave a height. Restricting just - // to IE7 to keep the 1px left/right space in other browsers. - // It is unclear where IE is getting the extra space that we need - // to negative-margin away, but so it goes. - *width: 100%; height: 1px; margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px - *margin: -5px 0 5px; overflow: hidden; background-color: @top; border-bottom: 1px solid @bottom; @@ -516,15 +416,12 @@ // Button backgrounds // ------------------ .buttonBackground(@startColor, @hoverColor: @startColor, @textColor: #fff, @textColorHover: @textColor) { - // gradientBar will set the background to a pleasing blend of these, to support IE<=9 - //.gradientBar(@startColor, @endColor, @textColor); color: @textColor; border-color: @startColor @startColor darken(@startColor, 15%); border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); - background-color: @startColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - .reset-filter(); + background-color: @startColor; .caret { border-top-color: @textColor; @@ -539,15 +436,8 @@ &.disabled, &[disabled] { color: @white; - background-color: @sand-2; + background-color: @sand-1; } - /* - // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves - &:active, - &.active { - background-color: darken(@endColor, 10%) e("\9"); - } - */ } // Navbar vertical align @@ -654,17 +544,14 @@ .offset (@columns) { margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); - *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); } .offsetFirstChild (@columns) { margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); - *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); } .span (@columns) { width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); - *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); } .row-fluid { @@ -674,7 +561,6 @@ .input-block-level(); float: left; margin-left: @fluidGridGutterWidth; - *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); } [class*="span"]:first-child { margin-left: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 8ef2c8c859..771be1bc2a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -129,10 +129,7 @@ outline: none; top: 0 !important; left: -100% !important; - width: 0px !important; - -webkit-transition: left 0.3s linear, left 0.3s ease-out; - -moz-transition: opacity 0.3s linear, top 0.3s ease-out; - -o-transition: opacity 0.3s linear, top 0.3s ease-out; + width: 0 !important; transition: opacity 0.3s linear, top 0.3s ease-out; height: 100% !important; } @@ -149,10 +146,7 @@ .umb-modal-left.fade { top: 0 !important; left: -100% !important; - width: 0px !important; - -webkit-transition: left 0.3s linear, left 0.3s ease-out; - -moz-transition: opacity 0.3s linear, top 0.3s ease-out; - -o-transition: opacity 0.3s linear, top 0.3s ease-out; + width: 0 !important; transition: opacity 0.3s linear, top 0.3s ease-out; height: 100% !important; } diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index 0101113670..a2710fab6c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -220,6 +220,7 @@ // DROPDOWNS // --------- .dropdown-menu { + display: block; border-radius: @dropdownBorderRadius; box-shadow: 0 5px 20px rgba(0,0,0,.3); padding-top: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 4e7937830c..e36acdc273 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -13,23 +13,25 @@ border: none; border-radius: 0; overflow-y: auto; - background-color: @purple-d2; + background-color: @blueNight; } .login-overlay__background-image { background-position: center center; background-repeat: no-repeat; background-size: cover; + background-image: url('../img/login.jpg'); width: 100%; height: 100%; position: absolute; - opacity: 0.05; } .login-overlay__logo { position: absolute; top: 22px; left: 25px; + width: 30px; + height: 30px; z-index: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index ae930d6fb0..a8c1efe1d3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -48,18 +48,20 @@ .form-search { flex: 1; - &__toggle{ + &__toggle { margin: 10px 0; display: flex; align-items: center; - input { - margin: 0; - } - label { + display: flex; + align-items: center; margin-left: 5px; margin-bottom: 0; + + input[type="checkbox"] { + margin: 0 5px 0 0; + } } } } @@ -142,9 +144,7 @@ /* tab buttons */ .umb-bottom-bar { background: @white; - -webkit-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - -moz-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); + box-shadow: 0 -18px 20px rgba(255, 255, 255, 1); border-top: 1px solid @gray-10; padding: 10px 0 10px 0; position: fixed; @@ -346,10 +346,16 @@ justify-content: center; align-items: center; background: @white; - border: 1px solid @gray-8; + border: 1px solid @ui-action-disgrete-border; animation: fadeIn 0.5s; border-radius: 3px; width: 50px; + &:hover { + .icon { + opacity: 0.8; + } + border-color: @ui-action-disgrete-border-hover; + } } .umb-panel-header-title-wrapper { @@ -382,8 +388,9 @@ } .umb-panel-header-icon .icon { - font-size: 30px; - color: @gray-7; + font-size: 30px; + color: @gray-7; + transition: opacity 120ms; } .umb-panel-header-icon-text { 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 8d1b70c35d..e021a577a5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -18,7 +18,16 @@ &-push { float:right; - } + } + + &--list{ + float: left; + } + + &__item{ + line-height: 1; + margin: 0 0 5px; + } } .umb-property-editor-tiny { @@ -69,6 +78,11 @@ } } +.umb-property .alert { + border-radius: 3px; +} + + // // Content picker @@ -213,7 +227,7 @@ margin: 24px 0 0; display: flex; } - + &__input { width: 100%; &-wrap{ @@ -284,7 +298,6 @@ line-height: 36px; text-align: center; - -moz-border-radius: 15px; border-radius: 15px; height: 32px; @@ -306,10 +319,8 @@ position: relative; display: flex; -ms-flex-direction: row; - -webkit-flex-direction: row; flex-direction: row; -ms-flex-wrap: wrap; - -webkit-flex-wrap: wrap; flex-wrap: wrap; justify-content: flex-start; } @@ -534,11 +545,7 @@ width: 100%; height: 100%; z-index: @zindexCropperOverlay - 1; - -moz-opacity: .75; opacity: .75; - filter: alpha(opacity=7); - -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); - -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); } @@ -547,7 +554,7 @@ height: 14px; text-align: center; border-radius: 20px; - background: @turquoise; + background: @pinkLight; border: 3px solid @white; opacity: 0.8; } @@ -884,6 +891,23 @@ .bootstrap-datetimepicker-widget .btn{padding: 0;} .bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;} .umb-datepicker .input-append .add-on{cursor: pointer;} +.umb-datepicker .input-append .on-top { + position: absolute; + margin-left: -31px; + margin-top: 1px; + display: inline-block; + height: 22px; + padding: 5px 6px 3px 6px; + font-size: @baseFontSize; + font-weight: normal; + line-height: @baseLineHeight; + text-align: center; + background-color: @white; + color: @ui-option-type; + &:hover { + color: @ui-option-type-hover; + } +} .umb-datepicker p {margin-top:10px;} .umb-datepicker p a{color: @gray-3;} diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index 9aae9840d1..ba8d02c1e1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -25,13 +25,6 @@ padding:10px; } -/* loader for macro loading in tinymce*/ -.umb-rte .mce-content-body .umb-macro-holder.loading { - background: url(img/loader.gif) right no-repeat; - background-size: 18px; - background-position-x: 99%; -} - .umb-rte .mce-container { box-sizing: border-box; } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index cf362a67f0..6388369b51 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -37,7 +37,8 @@ ul.sections>li>a::after { height: 4px; width: 100%; background-color: @pinkLight; - position: absolute; + position: absolute; + left: 0; bottom: -4px; border-radius: 3px 3px 0 0; opacity: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/tables.less b/src/Umbraco.Web.UI.Client/src/less/tables.less index fa8a44ec47..cd6304ef49 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tables.less +++ b/src/Umbraco.Web.UI.Client/src/less/tables.less @@ -62,6 +62,15 @@ table { } +.table tr > td:first-child { + border-left: 4px solid transparent; +} +.table tr.--selected > td:first-child { + border-left-color:@ui-selected-border; +} + + + // CONDENSED TABLE W/ HALF PADDING // ------------------------------- @@ -81,7 +90,6 @@ table { .table-bordered { border: 1px solid @tableBorder; border-collapse: separate; // Done so we can round those corners! - *border-collapse: collapse; // IE7 can't round corners anyway border-left: 0; box-shadow: none; .border-radius(@baseBorderRadius); diff --git a/src/Umbraco.Web.UI.Client/src/less/typeahead.less b/src/Umbraco.Web.UI.Client/src/less/typeahead.less index 0426c2966a..884f44974a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/typeahead.less +++ b/src/Umbraco.Web.UI.Client/src/less/typeahead.less @@ -3,30 +3,22 @@ .tt-hint { z-index: auto !important; margin-top:-4px !important; - width: 396px; - height: 30px; - padding: 8px 12px; - font-size: 24px; - line-height: 30px; - border: 2px solid @gray-8; - -webkit-border-radius: 2px !important; - -moz-border-radius: 2px !important; - border-radius: 2px !important; - outline: none; + width: 396px; + height: 30px; + padding: 8px 12px; + font-size: 24px; + line-height: 30px; + border: 2px solid @gray-8; + border-radius: 2px !important; + outline: none; } .typeahead { background-color: @white; } -.typeahead:focus { - /*border: 2px solid @blue;*/ -} - .tt-query { - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .tt-hint { @@ -39,12 +31,8 @@ padding: 8px 0; background-color: @white; border: 1px solid @gray-8; - -webkit-border-radius: 8px; - -moz-border-radius: 8px; - border-radius: 8px; - -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); - -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); - box-shadow: 0 5px 10px rgba(0,0,0,.2); + border-radius: 8px; + box-shadow: 0 5px 10px rgba(0,0,0,.2); } .tt-suggestion { diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 80665e4c64..19098b8a98 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -76,9 +76,10 @@ @gray-10: #F3F3F5; @gray-11: #F6F6F7; -@sand-1: hsl(22, 33%, 93%);// added 2019 +@sand-1: hsl(22, 18%, 84%);// added 2019 @sand-2: hsl(22, 34%, 88%);// added 2019 @sand-5: hsl(22, 31%, 93%);// added 2019 +@sand-6: hsl(22, 29%, 95%);// added 2019 @sand-7: hsl(22, 26%, 97%);// added 2019 @@ -138,8 +139,8 @@ @ui-active-type: @blueExtraDark; @ui-active-type-hover: @blueMid; -@ui-selected: @sand-1; -@ui-selected-hover: ligthen(@sand-1, 10); +@ui-selected: @sand-5; +@ui-selected-hover: ligthen(@sand-5, 10); @ui-selected-type: @blueExtraDark; @ui-selected-type-hover: @blueMid; @ui-selected-border: @pinkLight; @@ -176,7 +177,7 @@ @ui-btn-type: @white; @ui-btn-positive: @green; -@ui-btn-positive-hover: @green-l1; +@ui-btn-positive-hover: lighten(@green, 6%); @ui-btn-positive-type: @white; @ui-btn-negative: @red; @@ -507,7 +508,7 @@ @warningBorder: transparent; @errorText: @white; -@errorBackground: @red-d1; +@errorBackground: @red; @errorBorder: transparent; @successText: @white; diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index ecb011e3f0..001888f3ca 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -157,6 +157,7 @@ app.config(function ($routeProvider) { return; } + //TODO: Fix this special case by using components, the packager should be a component and then we just have a view for each route like normal rendering the component with the correct parameters //special case for the package section var packagePages = ["edit", "options"]; if ($routeParams.section.toLowerCase() === "packages" && $routeParams.tree.toLowerCase() === "packages" && packagePages.indexOf($routeParams.method.toLowerCase()) === -1) { @@ -164,6 +165,7 @@ app.config(function ($routeProvider) { return; } + //TODO: Fix this special case by using components, the users section should be a component and then we just have a view for each route like normal rendering the component with the correct parameters //special case for the users section var usersPages = ["user", "group"]; if ($routeParams.section.toLowerCase() === "users" && $routeParams.tree.toLowerCase() === "users" && usersPages.indexOf($routeParams.method.toLowerCase()) === -1) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index f38bb78929..56db1fd88a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -12,7 +12,7 @@
Tours
- +
@@ -38,7 +38,7 @@ {{ tour.name }}
- +
@@ -51,11 +51,11 @@
-
-
+
+
{{dashboard.label}}
+
-
{{property.caption}}
-
+
@@ -105,7 +105,7 @@ - +
Visit our.umbraco.com
The friendliest community diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js index 060e17a55e..83b73b408d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js @@ -78,7 +78,7 @@ // method to select a search result function selectResult(evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); } //callback when there are search results @@ -96,7 +96,7 @@ // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js index 9fbbad8f09..099439fa4b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js @@ -10,7 +10,7 @@ (function () { "use strict"; - function DataTypeSettingsController($scope, dataTypeResource, dataTypeHelper, localizationService) { + function DataTypeSettingsController($scope, dataTypeResource, dataTypeHelper, localizationService, notificationsService, overlayService, formHelper) { var vm = this; @@ -102,7 +102,7 @@ vm.saveButtonState = "busy"; var preValues = dataTypeHelper.createPreValueProps(vm.dataType.preValues); - + dataTypeResource.save(vm.dataType, preValues, $scope.model.create).then(function(newDataType) { $scope.model.dataType = newDataType; vm.saveButtonState = "success"; @@ -110,6 +110,19 @@ if ($scope.model && $scope.model.submit) { $scope.model.submit($scope.model); } + }, function(err) { + vm.saveButtonState = "error"; + + if(err.status === 400) { + if (err.data && (err.data.ModelState)) { + + formHelper.handleServerValidation(err.data.ModelState); + + for (var e in err.data.ModelState) { + notificationsService.error("Validation", err.data.ModelState[e][0]); + } + } + } }); } @@ -120,4 +133,4 @@ angular.module("umbraco").controller("Umbraco.Editors.DataTypeSettingsController", DataTypeSettingsController); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js index fb66552731..06a5f028ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js @@ -54,21 +54,22 @@ $scope.model.embed.preview = ""; - switch (response.data.Status) { + + switch (response.data.OEmbedStatus) { case 0: - //not supported - $scope.model.embed.info = "Not supported"; - break; + //not supported + $scope.model.embed.info = "Not supported"; + break; case 1: - //error - $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; - break; + //error + $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; + break; case 2: - $scope.model.embed.preview = response.data.Markup; - vm.trustedPreview = $sce.trustAsHtml(response.data.Markup); - $scope.model.embed.supportsDimensions = response.data.SupportsDimensions; - $scope.model.embed.success = true; - break; + $scope.model.embed.preview = response.data.Markup; + vm.trustedPreview = $sce.trustAsHtml(response.data.Markup); + $scope.model.embed.supportsDimensions = response.data.SupportsDimensions; + $scope.model.embed.success = true; + break; } }, function() { $scope.model.embed.supportsDimensions = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js index 16d6cf23fc..471d23ae84 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js @@ -49,7 +49,7 @@ function IconPickerController($scope, iconHelper, localizationService) { }); // set a default color if nothing is passed in - vm.color = $scope.model.color ? $scope.model.color : vm.colors[0].value; + vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors[0]; // if an icon is passed in - preselect it vm.icon = $scope.model.icon ? $scope.model.icon : undefined; @@ -70,8 +70,13 @@ function IconPickerController($scope, iconHelper, localizationService) { submit(); } + function findColor(value) { + return _.findWhere(vm.colors, {value: value}); + } + function selectColor(color, $index, $event) { - $scope.model.color = color; + $scope.model.color = color.value; + vm.color = color; } function close() { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 55c4317279..3caa6ae03d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -37,16 +37,16 @@ colors="vm.colors" selected-color="vm.color" size="s" - on-select="vm.selectColor"> + on-select="vm.selectColor(color)">
-
    +
    • - +
    • diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 6057b671bd..f4725fa82d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -75,7 +75,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", // get the content properties to build the anchor name list contentResource.getById(id).then(function (resp) { $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0]; + $scope.model.target.url = resp.urls[0].text; }); } } else if ($scope.model.target.url.length) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html index d6a5fbe230..1e75a4ba06 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html @@ -80,6 +80,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 7b3d13937f..2d6a2be471 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -104,7 +104,7 @@ angular.module("umbraco") } $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").trigger("click"); + angular.element(".umb-file-dropzone .file-select").trigger("click"); }; $scope.dragLeave = function(el, event) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 44b70ea613..da88b9321e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -32,8 +32,8 @@ no-dirty-check />
      -
      @@ -172,14 +172,15 @@ action="close()" button-style="link" shortcut="esc" - label="Close" + label-key="general_close" type="button"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js index c2a66dddf1..7e1e4b9047 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js @@ -79,7 +79,7 @@ // method to select a search result function selectResult(evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); } //callback when there are search results @@ -96,7 +96,7 @@ // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index 89987a3f27..b8581d28d0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -29,6 +29,12 @@ vm.submit = submit; vm.close = close; + vm.toggleAllowCultureVariants = toggleAllowCultureVariants; + vm.toggleValidation = toggleValidation; + vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile; + vm.toggleMemberCanEdit = toggleMemberCanEdit; + vm.toggleIsSensitiveData = toggleIsSensitiveData; + function onInit() { userService.getCurrentUser().then(function(user) { @@ -232,6 +238,30 @@ } + function toggleValue(settingValue) { + return !settingValue; + } + + function toggleAllowCultureVariants() { + $scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant); + } + + function toggleValidation() { + $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); + } + + function toggleShowOnMemberProfile() { + $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile); + } + + function toggleMemberCanEdit() { + $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit); + } + + function toggleIsSensitiveData() { + $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData); + } + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 4c7f2613b5..93d7936326 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -84,10 +84,19 @@
      - - + +
      + + + +
- +
- -
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index e179e0acb3..afec0ae120 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -20,6 +20,7 @@ vm.diff = null; vm.currentVersion = null; vm.rollbackButtonDisabled = true; + vm.labels = {}; // find the current version for invariant nodes if($scope.model.node.variants.length === 1) { @@ -39,12 +40,13 @@ } } - // set default title - if(!$scope.model.title) { - localizationService.localize("actions_rollback").then(function(value){ - $scope.model.title = value; - }); - } + localizationService.localizeMany(["actions_rollback", "general_choose"]).then(function (data) { + // set default title + if (!$scope.model.title) { + $scope.model.title = data[0]; + } + vm.labels.choose = data[1]; + }); // Load in diff library assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { @@ -177,4 +179,4 @@ angular.module("umbraco").controller("Umbraco.Editors.RollbackController", RollbackController); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index d7ba57c1af..9b230410b0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -40,7 +40,7 @@ ng-model="vm.selectedVersion" ng-options="version.displayValue for version in vm.previousVersions track by version.versionId" ng-change="vm.changeVersion(vm.selectedVersion)"> - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js index d6e8978e38..55d7a75000 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js @@ -78,7 +78,7 @@ function setSectionIcon(sections) { angular.forEach(sections, function(section) { - section.icon = "icon-section " + section.cssclass; + section.icon = "icon-section"; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index 404b4d8dd2..4af8c83983 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -1,5 +1,4 @@
-
@@ -79,7 +78,7 @@
-
+
  • @@ -127,7 +126,7 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index 2ce49880d7..d5dc203d67 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -2,10 +2,10 @@
- +
- - -

-
- +
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index 275c814761..829582329f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -1,7 +1,8 @@
-