diff --git a/.github/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md
new file mode 100644
index 0000000000..7d2afb46bf
--- /dev/null
+++ b/.github/CONTRIBUTION_GUIDELINES.md
@@ -0,0 +1,35 @@
+# Contributing to Umbraco CMS
+
+When you’re considering creating a pull request for Umbraco CMS, we will categorize them in two different sizes, small and large.
+
+The process for both sizes is very similar, as [explained in the contribution document](CONTRIBUTING.md#how-do-i-begin).
+
+## Small PRs
+Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files.
+
+We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process](REVIEW_PROCESS.md).
+
+### Up for grabs
+
+Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
+
+If you do start working on something, make sure leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
+
+## Large PRs
+New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.).
+
+We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
+
+Please make sure to describe your idea in an issue, it helps to put in mockup screenshots or videos.
+
+If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
+
+If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the [small PRs process](#small-prs) above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
+
+It is highly recommended that you speak to the HQ before making large, complex changes.
+
+### Pull request or package?
+
+If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and to fix bugs.
+
+Eventually, a package could "graduate" to be included in the CMS.
diff --git a/.github/ISSUE_TEMPLATE/1_Bug.md b/.github/ISSUE_TEMPLATE/1_Bug.md
index a1e33e3854..619452f700 100644
--- a/.github/ISSUE_TEMPLATE/1_Bug.md
+++ b/.github/ISSUE_TEMPLATE/1_Bug.md
@@ -12,7 +12,7 @@ thoroughly. Then, proceed by filling out the rest of the details in the issue
template below. The more details you can give us, the easier it will be for us
to determine the cause of a problem.
-See: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md
+See: https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/.github/CONTRIBUTING.md
-->
diff --git a/.github/REVIEW_PROCESS.md b/.github/REVIEW_PROCESS.md
new file mode 100644
index 0000000000..917d25b090
--- /dev/null
+++ b/.github/REVIEW_PROCESS.md
@@ -0,0 +1,25 @@
+# Review process
+
+You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response.
+
+In order to set some expectations, here's what happens next.
+
+## Review process
+
+You will get an initial reply within 48 hours (workdays) to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can.
+
+You will get feedback within at most 14 days after opening the PR. You’ll most likely get feedback sooner though. Then there are a few possible outcomes:
+
+- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
+- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
+- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
+- Your proposed change is awesome but.. not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!)
+
+## Are you still available?
+
+We understand you have other things to do and can't just drop everything to help us out.
+So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
+
+If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
+
+There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 585bd855b7..a0ff4d5b27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,7 +132,8 @@ src/Umbraco.Web.UI.Client/bower_components/*
preserve.belle
#Ignore Rule for output of generated documentation files from Grunt docserve
-src/Umbraco.Web.UI.Client/docs/api
+src/Umbraco.Web.UI.Docs/api
+src/Umbraco.Web.UI.Docs/package-lock.json
src/*.boltdata/
src/umbraco.sln.ide/*
src/.vs/
diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index 56462fcc40..fce15eb487 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -14,6 +14,7 @@
Contains the core assemblies needed to run Umbraco Cms
en-US
umbraco
+
@@ -44,7 +45,7 @@
-
+
@@ -55,6 +56,6 @@
-
+
diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index 614a816f3f..c8374bc2f7 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -14,6 +14,7 @@
Contains the core assemblies needed to run Umbraco Cms
en-US
umbraco
+
@@ -24,10 +25,10 @@
not want this to happen as the alpha of the next major is, really, the next major already.
-->
-
-
+
+
-
+
@@ -43,7 +44,7 @@
-
+
@@ -58,7 +59,7 @@
-
-
+
+
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index 0d18ac59fa..b7bfaaff5b 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -14,6 +14,7 @@
Installs Umbraco Cms in your Visual Studio ASP.NET project
en-US
umbraco
+
diff --git a/build/NuSpecs/tools/Readme.txt b/build/NuSpecs/tools/Readme.txt
index e40b0dbc7e..b0583a2b4d 100644
--- a/build/NuSpecs/tools/Readme.txt
+++ b/build/NuSpecs/tools/Readme.txt
@@ -1,11 +1,11 @@
- 888
- 888
-888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
-888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
-888 888 888 888 888 888 888 888 .d888888 888 888 888
-Y88b 888 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
- "Y88888 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
+ 888
+ 888
+888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
+888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
+888 888 888 888 888 888 888 888 .d888888 888 888 888
+Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
+ "Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
------------------------------------------------------------------
diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt
index df364c64ed..2f52d03776 100644
--- a/build/NuSpecs/tools/ReadmeUpgrade.txt
+++ b/build/NuSpecs/tools/ReadmeUpgrade.txt
@@ -1,12 +1,13 @@
- _ _ __ __ ____ _____ _____ ____
- | | | | \/ | _ \| __ \ /\ / ____/ __ \
- | | | | \ / | |_) | |__) | / \ | | | | | |
- | | | | |\/| | _ <| _ / / /\ \| | | | | |
- | |__| | | | | |_) | | \ \ / ____ | |___| |__| |
- \____/|_| |_|____/|_| \_/_/ \_\_____\____/
+ 888
+ 888
+888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
+888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
+888 888 888 888 888 888 888 888 .d888888 888 888 888
+Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
+ "Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
-----------------------------------------------------
+------------------------------------------------------------------
Don't forget to build!
diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt
index 4d660301a8..828bb8612f 100644
--- a/build/NuSpecs/tools/Views.Web.config.install.xdt
+++ b/build/NuSpecs/tools/Views.Web.config.install.xdt
@@ -8,7 +8,7 @@
-
+
@@ -18,13 +18,13 @@
-
+
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index 14778e0f10..f0bfb01585 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -128,6 +128,7 @@
+
diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1
index 1411cb5c97..0be28f9467 100644
--- a/build/NuSpecs/tools/install.ps1
+++ b/build/NuSpecs/tools/install.ps1
@@ -18,9 +18,20 @@ if ($project) {
# Copy umbraco and umbraco_files from package to project folder
$umbracoFolder = Join-Path $projectPath "Umbraco"
- New-Item -ItemType Directory -Force -Path $umbracoFolder
+ New-Item -ItemType Directory -Force -Path $umbracoFolder
$umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco"
- robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log
+
+ Write-Host "copying files to $umbracoFolder ..."
+ # see https://support.microsoft.com/en-us/help/954404/return-codes-that-are-used-by-the-robocopy-utility-in-windows-server-2
+ robocopy $umbracoFolderSource $umbracoFolder /is /it /e
+ if (($lastexitcode -eq 1) -or ($lastexitcode -eq 3) -or ($lastexitcode -eq 5) -or ($lastexitcode -eq 7))
+ {
+ write-host "Copy succeeded!"
+ }
+ else
+ {
+ write-host "Copy failed with exit code:" $lastexitcode
+ }
$copyWebconfig = $true
$destinationWebConfig = Join-Path $projectPath "Web.config"
@@ -40,7 +51,11 @@ if ($project) {
}
}
}
- Catch { }
+ Catch
+ {
+ Write-Host "An error occurred:"
+ Write-Host $_
+ }
}
if($copyWebconfig -eq $true)
@@ -74,7 +89,9 @@ if ($project) {
}
Catch
{
- # Not a big problem if this fails, let it go
+ # Not a big problem if this fails, let it go
+ # Write-Host "An error occurred:"
+ # Write-Host $_
}
}
diff --git a/build/build-docs.ps1 b/build/build-docs.ps1
deleted file mode 100644
index 8cd3f090c7..0000000000
--- a/build/build-docs.ps1
+++ /dev/null
@@ -1,44 +0,0 @@
-$uenv=build/build.ps1 -get
-
-$src = "$($uenv.SolutionRoot)\src"
-$tmp = $uenv.BuildTemp
-$out = $uenv.BuildOutput
-$DocFxJson = "$src\ApiDocs\docfx.json"
-$DocFxSiteOutput = "$tmp\_site\*.*"
-
-################ Do the UI docs
-$uenv.CompileBelle()
-
-"Moving to Umbraco.Web.UI.Client folder"
-cd .\src\Umbraco.Web.UI.Client
-
-"Generating the docs and waiting before executing the next commands"
-& gulp docs | Out-Null
-
-# change baseUrl
-$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
-$IndexPath = "./docs/api/index.html"
-(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
-
-# zip it
-& $uenv.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*"
-
-
-################ Do the c# docs
-
-# Build the solution in debug mode
-$SolutionPath = Join-Path -Path $src -ChildPath "umbraco.sln"
-#$uenv.CompileUmbraco()
-
-#restore nuget packages
-$uenv.RestoreNuGet()
-
-# run DocFx
-$DocFx = $uenv.BuildEnv.DocFx
-
-Write-Host "$DocFxJson"
-& $DocFx metadata $DocFxJson
-& $DocFx build $DocFxJson
-
-# zip it
-& $uenv.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput
diff --git a/build/build.ps1 b/build/build.ps1
index 55b686c98e..892654d3cd 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -15,7 +15,7 @@
[Parameter(Mandatory=$false)]
[Alias("doc")]
[switch] $docfx = $false,
-
+
# keep the build directories, don't clear them
[Parameter(Mandatory=$false)]
[Alias("c")]
@@ -392,13 +392,13 @@
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" `
-Properties BuildTmp="$($this.BuildTemp)" `
-Version "$($this.Version.Semver.ToString())" `
- -Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log"
+ -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log"
if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." }
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" `
-Properties BuildTmp="$($this.BuildTemp)" `
-Version "$($this.Version.Semver.ToString())" `
- -Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log"
+ -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log"
if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." }
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" `
@@ -429,52 +429,51 @@
Write-Host "Prepare Azure Gallery"
$this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput)
})
-
+
$ubuild.DefineMethod("PrepareCSharpDocs",
{
Write-Host "Prepare C# Documentation"
-
+
$src = "$($this.SolutionRoot)\src"
$tmp = $this.BuildTemp
$out = $this.BuildOutput
$DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json"
$DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*"
-
+
#restore nuget packages
$this.RestoreNuGet()
# run DocFx
$DocFx = $this.BuildEnv.DocFx
-
+
& $DocFx metadata $DocFxJson
& $DocFx build $DocFxJson
# zip it
& $this.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput
})
-
+
$ubuild.DefineMethod("PrepareAngularDocs",
{
Write-Host "Prepare Angular Documentation"
-
- $src = "$($this.SolutionRoot)\src"
- $out = $this.BuildOutput
- $this.CompileBelle()
-
- "Moving to Umbraco.Web.UI.Client folder"
- cd .\src\Umbraco.Web.UI.Client
+ $src = "$($this.SolutionRoot)\src"
+ $out = $this.BuildOutput
+
+ "Moving to Umbraco.Web.UI.Docs folder"
+ cd ..\src\Umbraco.Web.UI.Docs
"Generating the docs and waiting before executing the next commands"
- & gulp docs | Out-Null
+ & npm install
+ & npx gulp docs
# change baseUrl
$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
- $IndexPath = "./docs/api/index.html"
+ $IndexPath = "./api/index.html"
(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
# zip it
- & $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*"
+ & $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Docs\api\*.*"
})
$ubuild.DefineMethod("Build",
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 9ed398d52f..bf3a271d32 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -18,5 +18,5 @@ using System.Resources;
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
-[assembly: AssemblyFileVersion("8.1.0")]
-[assembly: AssemblyInformationalVersion("8.1.0")]
+[assembly: AssemblyFileVersion("8.3.0")]
+[assembly: AssemblyInformationalVersion("8.3.0")]
diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs
index 158b132f26..6dd866705e 100644
--- a/src/Umbraco.Core/AsyncLock.cs
+++ b/src/Umbraco.Core/AsyncLock.cs
@@ -67,31 +67,34 @@ namespace Umbraco.Core
: new NamedSemaphoreReleaser(_semaphore2);
}
- public Task LockAsync()
- {
- var wait = _semaphore != null
- ? _semaphore.WaitAsync()
- : _semaphore2.WaitOneAsync();
+ //NOTE: We don't use the "Async" part of this lock at all
+ //TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again
- return wait.IsCompleted
- ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
- : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
- this, CancellationToken.None,
- TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
- }
+ //public Task LockAsync()
+ //{
+ // var wait = _semaphore != null
+ // ? _semaphore.WaitAsync()
+ // : _semaphore2.WaitOneAsync();
- public Task LockAsync(int millisecondsTimeout)
- {
- var wait = _semaphore != null
- ? _semaphore.WaitAsync(millisecondsTimeout)
- : _semaphore2.WaitOneAsync(millisecondsTimeout);
+ // return wait.IsCompleted
+ // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
+ // : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
+ // this, CancellationToken.None,
+ // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
+ //}
- return wait.IsCompleted
- ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
- : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
- this, CancellationToken.None,
- TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
- }
+ //public Task LockAsync(int millisecondsTimeout)
+ //{
+ // var wait = _semaphore != null
+ // ? _semaphore.WaitAsync(millisecondsTimeout)
+ // : _semaphore2.WaitOneAsync(millisecondsTimeout);
+
+ // return wait.IsCompleted
+ // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
+ // : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
+ // this, CancellationToken.None,
+ // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
+ //}
public IDisposable Lock()
{
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/FileSystems.cs b/src/Umbraco.Core/Composing/CompositionExtensions/FileSystems.cs
index 078a505be9..8518d907b5 100644
--- a/src/Umbraco.Core/Composing/CompositionExtensions/FileSystems.cs
+++ b/src/Umbraco.Core/Composing/CompositionExtensions/FileSystems.cs
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
// register the IFileSystem supporting the IMediaFileSystem
// THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM
// and, SupportingFileSystem.For() returns the underlying filesystem
- composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
+ composition.SetMediaFileSystem(() => new PhysicalFileSystem(SystemDirectories.Media));
return composition;
}
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs
index 0baefe104b..d252c58730 100644
--- a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs
+++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs
@@ -96,7 +96,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
var pluginLangFolders = appPlugins.Exists == false
? Enumerable.Empty()
: appPlugins.GetDirectories()
- .SelectMany(x => x.GetDirectories("Lang"))
+ .SelectMany(x => x.GetDirectories("Lang", SearchOption.AllDirectories))
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
diff --git a/src/Umbraco.Core/Composing/Lifetime.cs b/src/Umbraco.Core/Composing/Lifetime.cs
index 1a2cc3119a..012555be5e 100644
--- a/src/Umbraco.Core/Composing/Lifetime.cs
+++ b/src/Umbraco.Core/Composing/Lifetime.cs
@@ -12,31 +12,62 @@
/// or MS.DI, PerDependency in Autofac.
Transient,
+ // TODO: We need to fix this up, currently LightInject is the only one that behaves differently from all other containers.
+ // ... the simple fix would be to map this to PerScopeLifetime in LI but need to wait on a response here https://github.com/seesharper/LightInject/issues/494#issuecomment-518942625
+ //
+ // we use it for controllers, httpContextBase and other request scoped objects: MembershpHelper, TagQuery, UmbracoTreeSearcher and ISearchableTree
+ // - so that they are automatically disposed at the end of the scope (ie request)
+ // - not sure they should not be simply 'scoped'?
+
///
/// One unique instance per request.
///
- // TODO: review lifetimes for LightInject vs other containers
- // currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope'
- // but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped
- //
- // we use it for controllers, httpContextBase and umbracoContext
- // - so that they are automatically disposed at the end of the scope (ie request)
- // - not sure they should not be simply 'scoped'?
- //
- // Castle has an extra PerWebRequest something, and others use scope
- // what about Request before first request ie during application startup?
- // see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/
- // Castle ends up requiring a special scope manager too
- // see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4
- //
- // but maybe also - why are we requiring scoped services at startup?
+ ///
+ ///
+ /// Any instance created with this lifetime will be disposed at the end of a request.
+ ///
+ /// Corresponds to
+ ///
+ /// PerRequestLifeTime in LightInject - means transient but disposed at the end of the current web request.
+ /// see: https://github.com/seesharper/LightInject/issues/494#issuecomment-518493262
+ ///
+ ///
+ /// Scoped in MS.DI - means one per web request.
+ /// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes
+ ///
+ /// InstancePerRequest in Autofac - means one per web request.
+ /// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
+ /// But "Behind the scenes, though, it’s still just instance per matching lifetime scope."
+ ///
+ ///
+ /// LifestylePerWebRequest in Castle Windsor - means one per web request.
+ /// see https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-part-7-lifestyles.md#the-perwebrequest-lifestyle
+ ///
+ ///
Request,
///
- /// One unique instance per container scope.
+ /// One unique instance per scope.
///
- /// Corresponds to Scope in LightInject, Scoped in MS.DI
- /// or Castle Windsor, PerLifetimeScope in Autofac.
+ ///
+ ///
+ /// Any instance created with this lifetime will be disposed at the end of the current scope.
+ ///
+ /// Corresponds to
+ /// PerScopeLifetime in LightInject (when in a request, means one per web request)
+ ///
+ /// Scoped in MS.DI (when in a request, means one per web request)
+ /// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes
+ ///
+ /// InstancePerLifetimeScope in Autofac (when in a request, means one per web request)
+ /// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-lifetime-scope
+ /// Also note that Autofac's InstancePerRequest is the same as this, see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
+ /// it says "Behind the scenes, though, it’s still just instance per matching lifetime scope."
+ ///
+ ///
+ /// LifestyleScoped in Castle Windsor
+ ///
+ ///
Scope,
///
diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs
index af9277fce9..fe7a561eca 100644
--- a/src/Umbraco.Core/Composing/TypeLoader.cs
+++ b/src/Umbraco.Core/Composing/TypeLoader.cs
@@ -42,8 +42,8 @@ namespace Umbraco.Core.Composing
private string _currentAssembliesHash;
private IEnumerable _assemblies;
private bool _reportedChange;
- private static string _localTempPath;
- private static string _fileBasePath;
+ private readonly string _localTempPath;
+ private string _fileBasePath;
///
/// Initializes a new instance of the class.
diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs
index 828a577c34..5dd33c2a60 100644
--- a/src/Umbraco.Core/CompositionExtensions.cs
+++ b/src/Umbraco.Core/CompositionExtensions.cs
@@ -4,6 +4,7 @@ using Umbraco.Core.Composing;
using Umbraco.Core.Dictionary;
using Umbraco.Core.IO;
using Umbraco.Core.Logging.Viewer;
+using Umbraco.Core.Manifest;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PackageActions;
using Umbraco.Core.Persistence.Mappers;
@@ -66,9 +67,16 @@ namespace Umbraco.Core
/// Gets the validators collection builder.
///
/// The composition.
- internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition)
+ internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this Composition composition)
=> composition.WithCollectionBuilder();
+ ///
+ /// Gets the manifest filter collection builder.
+ ///
+ /// The composition.
+ public static ManifestFilterCollectionBuilder ManifestFilters(this Composition composition)
+ => composition.WithCollectionBuilder();
+
///
/// Gets the components collection builder.
///
diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs
index 6fdf7ea3b9..d1672c6c7f 100644
--- a/src/Umbraco.Core/ConfigsExtensions.cs
+++ b/src/Umbraco.Core/ConfigsExtensions.cs
@@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.HealthChecks;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
+using Umbraco.Core.Manifest;
namespace Umbraco.Core
{
@@ -41,7 +42,12 @@ namespace Umbraco.Core
configs.Add(() => new CoreDebug());
// GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition
- configs.Add(factory => new GridConfig(factory.GetInstance(), factory.GetInstance(), configDir, factory.GetInstance().Debug));
+ configs.Add(factory => new GridConfig(
+ factory.GetInstance(),
+ factory.GetInstance(),
+ configDir,
+ factory.GetInstance(),
+ factory.GetInstance().Debug));
}
}
}
diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index 49f4481a59..a888e3c42b 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -33,7 +33,6 @@ namespace Umbraco.Core.Configuration
///
private static void ResetInternal()
{
- GlobalSettingsExtensions.Reset();
_reservedPaths = null;
_reservedUrls = null;
HasSmtpServer = null;
diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
index 6bfb7ea904..bc76caacee 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Web;
using System.Web.Routing;
using Umbraco.Core.IO;
@@ -9,22 +11,9 @@ namespace Umbraco.Core.Configuration
{
public static class GlobalSettingsExtensions
{
- ///
- /// Used in unit testing to reset all config items, this is automatically called by GlobalSettings.Reset()
- ///
- internal static void Reset()
- {
- _reservedUrlsCache = null;
- _mvcArea = null;
- }
-
- private static readonly object Locker = new object();
- //make this volatile so that we can ensure thread safety with a double check lock
- private static volatile string _reservedUrlsCache;
- private static string _reservedPathsCache;
- private static HashSet _reservedList = new HashSet();
private static string _mvcArea;
+
///
/// This returns the string of the MVC Area route.
///
@@ -40,6 +29,13 @@ namespace Umbraco.Core.Configuration
{
if (_mvcArea != null) return _mvcArea;
+ _mvcArea = GetUmbracoMvcAreaNoCache(globalSettings);
+
+ return _mvcArea;
+ }
+
+ internal static string GetUmbracoMvcAreaNoCache(this IGlobalSettings globalSettings)
+ {
if (globalSettings.Path.IsNullOrWhiteSpace())
{
throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified");
@@ -48,95 +44,8 @@ namespace Umbraco.Core.Configuration
var path = globalSettings.Path;
if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518
path = path.Substring(SystemDirectories.Root.Length);
- _mvcArea = path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
- return _mvcArea;
+ return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
}
- ///
- /// Determines whether the specified URL is reserved or is inside a reserved path.
- ///
- ///
- /// The URL to check.
- ///
- /// true if the specified URL is reserved; otherwise, false.
- ///
- internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url)
- {
- if (_reservedUrlsCache == null)
- {
- lock (Locker)
- {
- if (_reservedUrlsCache == null)
- {
- // store references to strings to determine changes
- _reservedPathsCache = globalSettings.ReservedPaths;
- _reservedUrlsCache = globalSettings.ReservedUrls;
-
- // add URLs and paths to a new list
- var newReservedList = new HashSet();
- foreach (var reservedUrlTrimmed in _reservedUrlsCache
- .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
- .Select(x => x.Trim().ToLowerInvariant())
- .Where(x => x.IsNullOrWhiteSpace() == false)
- .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/"))
- .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false))
- {
- newReservedList.Add(reservedUrlTrimmed);
- }
-
- foreach (var reservedPathTrimmed in _reservedPathsCache
- .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
- .Select(x => x.Trim().ToLowerInvariant())
- .Where(x => x.IsNullOrWhiteSpace() == false)
- .Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/"))
- .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false))
- {
- newReservedList.Add(reservedPathTrimmed);
- }
-
- // use the new list from now on
- _reservedList = newReservedList;
- }
- }
- }
-
- //The url should be cleaned up before checking:
- // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
- // * We shouldn't be comparing the query at all
- var pathPart = url.Split(new[] {'?'}, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant();
- if (pathPart.Contains(".") == false)
- {
- pathPart = pathPart.EnsureEndsWith('/');
- }
-
- // return true if url starts with an element of the reserved list
- return _reservedList.Any(x => pathPart.InvariantStartsWith(x));
- }
-
- ///
- /// Determines whether the current request is reserved based on the route table and
- /// whether the specified URL is reserved or is inside a reserved path.
- ///
- ///
- ///
- ///
- /// The route collection to lookup the request in
- ///
- internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url, HttpContextBase httpContext, RouteCollection routes)
- {
- if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
- 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;
-
- //continue with the standard ignore routine
- return globalSettings.IsReservedPathOrUrl(url);
- }
-
-
}
}
diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs
index b2dae09fc9..9aead74886 100644
--- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs
+++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs
@@ -1,14 +1,15 @@
using System.IO;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
+using Umbraco.Core.Manifest;
namespace Umbraco.Core.Configuration.Grid
{
class GridConfig : IGridConfig
{
- public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug)
+ public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, ManifestParser manifestParser, bool isDebug)
{
- EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, isDebug);
+ EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, manifestParser, isDebug);
}
public IGridEditorsConfig EditorsConfig { get; }
diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs
index 0e7ef62c58..d434da8c70 100644
--- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs
+++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
-using Newtonsoft.Json.Linq;
using Umbraco.Core.Cache;
-using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.PropertyEditors;
@@ -15,13 +13,15 @@ namespace Umbraco.Core.Configuration.Grid
private readonly ILogger _logger;
private readonly AppCaches _appCaches;
private readonly DirectoryInfo _configFolder;
+ private readonly ManifestParser _manifestParser;
private readonly bool _isDebug;
- public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug)
+ public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, ManifestParser manifestParser, bool isDebug)
{
_logger = logger;
_appCaches = appCaches;
_configFolder = configFolder;
+ _manifestParser = manifestParser;
_isDebug = isDebug;
}
@@ -31,9 +31,6 @@ namespace Umbraco.Core.Configuration.Grid
{
List GetResult()
{
- // TODO: should use the common one somehow! + ignoring _appPlugins here!
- var parser = new ManifestParser(_appCaches, Current.ManifestValidators, _logger);
-
var editors = new List();
var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js");
if (File.Exists(gridConfig))
@@ -42,7 +39,7 @@ namespace Umbraco.Core.Configuration.Grid
try
{
- editors.AddRange(parser.ParseGridEditors(sourceString));
+ editors.AddRange(_manifestParser.ParseGridEditors(sourceString));
}
catch (Exception ex)
{
@@ -51,7 +48,7 @@ namespace Umbraco.Core.Configuration.Grid
}
// add manifest editors, skip duplicates
- foreach (var gridEditor in parser.Manifest.GridEditors)
+ foreach (var gridEditor in _manifestParser.Manifest.GridEditors)
{
if (editors.Contains(gridEditor) == false) editors.Add(gridEditor);
}
diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs
index f2b31be28f..673da8f9a3 100644
--- a/src/Umbraco.Core/Constants-DataTypes.cs
+++ b/src/Umbraco.Core/Constants-DataTypes.cs
@@ -1,27 +1,375 @@
-namespace Umbraco.Core
+using System;
+
+namespace Umbraco.Core
{
public static partial class Constants
{
public static class DataTypes
{
- public const int LabelString = -92;
+ //NOTE: unfortunately due to backwards compat we can't move/rename these, with the addition of the GUID
+ //constants, it would make more sense to have these suffixed with "ID" or in a Subclass called "INT", for
+ //now all we can do is make a subclass called Guids to put the GUID IDs.
+
+ public const int LabelString = System.DefaultLabelDataTypeId;
public const int LabelInt = -91;
public const int LabelBigint = -93;
public const int LabelDateTime = -94;
public const int LabelTime = -98;
public const int LabelDecimal = -99;
+ public const int Textarea = -89;
public const int Textbox = -88;
+ public const int RichtextEditor = -87;
public const int Boolean = -49;
public const int DateTime = -36;
public const int DropDownSingle = -39;
public const int DropDownMultiple = -42;
+ public const int Upload = -90;
public const int DefaultContentListView = -95;
public const int DefaultMediaListView = -96;
public const int DefaultMembersListView = -97;
+ public const int ImageCropper = 1043;
public const int Tags = 1041;
+
+ public static class ReservedPreValueKeys
+ {
+ public const string IgnoreUserStartNodes = "ignoreUserStartNodes";
+ }
+
+ ///
+ /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management.
+ ///
+ public static class Guids
+ {
+
+ ///
+ /// Guid for Content Picker as string
+ ///
+ public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59";
+
+ ///
+ /// Guid for Content Picker
+ ///
+ public static readonly Guid ContentPickerGuid = new Guid(ContentPicker);
+
+
+ ///
+ /// Guid for Member Picker as string
+ ///
+ public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548";
+
+ ///
+ /// Guid for Member Picker
+ ///
+ public static readonly Guid MemberPickerGuid = new Guid(MemberPicker);
+
+
+ ///
+ /// Guid for Media Picker as string
+ ///
+ public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5";
+
+ ///
+ /// Guid for Media Picker
+ ///
+ public static readonly Guid MediaPickerGuid = new Guid(MediaPicker);
+
+
+ ///
+ /// Guid for Multiple Media Picker as string
+ ///
+ public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A";
+
+ ///
+ /// Guid for Multiple Media Picker
+ ///
+ public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker);
+
+
+ ///
+ /// Guid for Related Links as string
+ ///
+ public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F";
+
+ ///
+ /// Guid for Related Links
+ ///
+ public static readonly Guid RelatedLinksGuid = new Guid(RelatedLinks);
+
+
+ ///
+ /// Guid for Member as string
+ ///
+ public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda";
+
+ ///
+ /// Guid for Member
+ ///
+ public static readonly Guid MemberGuid = new Guid(Member);
+
+
+ ///
+ /// Guid for Image Cropper as string
+ ///
+ public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f";
+
+ ///
+ /// Guid for Image Cropper
+ ///
+ public static readonly Guid ImageCropperGuid = new Guid(ImageCropper);
+
+
+ ///
+ /// Guid for Tags as string
+ ///
+ public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549";
+
+ ///
+ /// Guid for Tags
+ ///
+ public static readonly Guid TagsGuid = new Guid(Tags);
+
+
+ ///
+ /// Guid for List View - Content as string
+ ///
+ public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4";
+
+ ///
+ /// Guid for List View - Content
+ ///
+ public static readonly Guid ListViewContentGuid = new Guid(ListViewContent);
+
+
+ ///
+ /// Guid for List View - Media as string
+ ///
+ public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF";
+
+ ///
+ /// Guid for List View - Media
+ ///
+ public static readonly Guid ListViewMediaGuid = new Guid(ListViewMedia);
+
+
+ ///
+ /// Guid for List View - Members as string
+ ///
+ public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D";
+
+ ///
+ /// Guid for List View - Members
+ ///
+ public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers);
+
+
+ ///
+ /// Guid for Date Picker with time as string
+ ///
+ public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a";
+
+ ///
+ /// Guid for Date Picker with time
+ ///
+ public static readonly Guid DatePickerWithTimeGuid = new Guid(DatePickerWithTime);
+
+
+ ///
+ /// Guid for Approved Color as string
+ ///
+ public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17";
+
+ ///
+ /// Guid for Approved Color
+ ///
+ public static readonly Guid ApprovedColorGuid = new Guid(ApprovedColor);
+
+
+ ///
+ /// Guid for Dropdown multiple as string
+ ///
+ public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53";
+
+ ///
+ /// Guid for Dropdown multiple
+ ///
+ public static readonly Guid DropdownMultipleGuid = new Guid(DropdownMultiple);
+
+
+ ///
+ /// Guid for Radiobox as string
+ ///
+ public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805";
+
+ ///
+ /// Guid for Radiobox
+ ///
+ public static readonly Guid RadioboxGuid = new Guid(Radiobox);
+
+
+ ///
+ /// Guid for Date Picker as string
+ ///
+ public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1";
+
+ ///
+ /// Guid for Date Picker
+ ///
+ public static readonly Guid DatePickerGuid = new Guid(DatePicker);
+
+
+ ///
+ /// Guid for Dropdown as string
+ ///
+ public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03";
+
+ ///
+ /// Guid for Dropdown
+ ///
+ public static readonly Guid DropdownGuid = new Guid(Dropdown);
+
+
+ ///
+ /// Guid for Checkbox list as string
+ ///
+ public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a";
+
+ ///
+ /// Guid for Checkbox list
+ ///
+ public static readonly Guid CheckboxListGuid = new Guid(CheckboxList);
+
+
+ ///
+ /// Guid for Checkbox as string
+ ///
+ public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49";
+
+ ///
+ /// Guid for Checkbox
+ ///
+ public static readonly Guid CheckboxGuid = new Guid(Checkbox);
+
+
+ ///
+ /// Guid for Numeric as string
+ ///
+ public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5";
+
+ ///
+ /// Guid for Dropdown
+ ///
+ public static readonly Guid NumericGuid = new Guid(Numeric);
+
+
+ ///
+ /// Guid for Richtext editor as string
+ ///
+ public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad";
+
+ ///
+ /// Guid for Richtext editor
+ ///
+ public static readonly Guid RichtextEditorGuid = new Guid(RichtextEditor);
+
+
+ ///
+ /// Guid for Textstring as string
+ ///
+ public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae";
+
+ ///
+ /// Guid for Textstring
+ ///
+ public static readonly Guid TextstringGuid = new Guid(Textstring);
+
+
+ ///
+ /// Guid for Textarea as string
+ ///
+ public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3";
+
+ ///
+ /// Guid for Dropdown
+ ///
+ public static readonly Guid TextareaGuid = new Guid(Textarea);
+
+
+ ///
+ /// Guid for Upload as string
+ ///
+ public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a";
+
+ ///
+ /// Guid for Upload
+ ///
+ public static readonly Guid UploadGuid = new Guid(Upload);
+
+
+ ///
+ /// Guid for Label as string
+ ///
+ public const string LabelString = "f0bc4bfb-b499-40d6-ba86-058885a5178c";
+
+ ///
+ /// Guid for Label string
+ ///
+ public static readonly Guid LabelStringGuid = new Guid(LabelString);
+
+ ///
+ /// Guid for Label as int
+ ///
+ public const string LabelInt = "8e7f995c-bd81-4627-9932-c40e568ec788";
+
+ ///
+ /// Guid for Label int
+ ///
+ public static readonly Guid LabelIntGuid = new Guid(LabelInt);
+
+ ///
+ /// Guid for Label as big int
+ ///
+ public const string LabelBigInt = "930861bf-e262-4ead-a704-f99453565708";
+
+ ///
+ /// Guid for Label big int
+ ///
+ public static readonly Guid LabelBigIntGuid = new Guid(LabelBigInt);
+
+ ///
+ /// Guid for Label as date time
+ ///
+ public const string LabelDateTime = "0e9794eb-f9b5-4f20-a788-93acd233a7e4";
+
+ ///
+ /// Guid for Label date time
+ ///
+ public static readonly Guid LabelDateTimeGuid = new Guid(LabelDateTime);
+
+ ///
+ /// Guid for Label as time
+ ///
+ public const string LabelTime = "a97cec69-9b71-4c30-8b12-ec398860d7e8";
+
+ ///
+ /// Guid for Label time
+ ///
+ public static readonly Guid LabelTimeGuid = new Guid(LabelTime);
+
+ ///
+ /// Guid for Label as decimal
+ ///
+ public const string LabelDecimal = "8f1ef1e1-9de4-40d3-a072-6673f631ca64";
+
+ ///
+ /// Guid for Label decimal
+ ///
+ public static readonly Guid LabelDecimalGuid = new Guid(LabelDecimal);
+
+
+ }
}
}
}
diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs
index d3e8b4ad3b..05213ed1c4 100644
--- a/src/Umbraco.Core/Constants-Icons.cs
+++ b/src/Umbraco.Core/Constants-Icons.cs
@@ -5,39 +5,89 @@
public static class Icons
{
///
- /// System contenttype icon
+ /// System default icon
///
- public const string ContentType = "icon-arrangement";
+ public const string DefaultIcon = Content;
///
- /// System datatype icon
+ /// System content icon
+ ///
+ public const string Content = "icon-document";
+
+ ///
+ /// System content type icon
+ ///
+ public const string ContentType = "icon-item-arrangement";
+
+ ///
+ /// System data type icon
///
public const string DataType = "icon-autofill";
///
- /// System property editor icon
+ /// System list view icon
///
- public const string PropertyEditor = "icon-autofill";
+ public const string ListView = "icon-thumbnail-list";
///
/// System macro icon
///
public const string Macro = "icon-settings-alt";
+ ///
+ /// System media file icon
+ ///
+ public const string MediaFile = "icon-document";
+
+ ///
+ /// System media folder icon
+ ///
+ public const string MediaFolder = "icon-folder";
+
+ ///
+ /// System media image icon
+ ///
+ public const string MediaImage = "icon-picture";
+
+ ///
+ /// System media type icon
+ ///
+ public const string MediaType = "icon-thumbnails";
+
///
/// System member icon
///
public const string Member = "icon-user";
///
- /// System member icon
+ /// System member group icon
+ ///
+ public const string MemberGroup = "icon-users-alt";
+
+ ///
+ /// System member type icon
///
public const string MemberType = "icon-users";
+ ///
+ /// System property editor icon
+ ///
+ public const string PropertyEditor = "icon-autofill";
+
///
/// System member icon
///
public const string Template = "icon-layout";
+
+ ///
+ /// System user icon
+ ///
+ public const string User = "icon-user";
+
+ ///
+ /// System user group icon
+ ///
+ public const string UserGroup = "icon-users";
}
}
}
diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs
index 0c2e246721..b55dc0ca18 100644
--- a/src/Umbraco.Core/Constants-PropertyEditors.cs
+++ b/src/Umbraco.Core/Constants-PropertyEditors.cs
@@ -14,6 +14,23 @@ namespace Umbraco.Core
///
public const string InternalGenericPropertiesPrefix = "_umb_";
+ public static class Legacy
+ {
+ public static class Aliases
+ {
+ public const string Textbox = "Umbraco.Textbox";
+ public const string Date = "Umbraco.Date";
+ public const string ContentPicker2 = "Umbraco.ContentPicker2";
+ public const string MediaPicker2 = "Umbraco.MediaPicker2";
+ public const string MemberPicker2 = "Umbraco.MemberPicker2";
+ public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2";
+ public const string TextboxMultiple = "Umbraco.TextboxMultiple";
+ public const string RelatedLinks2 = "Umbraco.RelatedLinks2";
+ public const string RelatedLinks = "Umbraco.RelatedLinks";
+
+ }
+ }
+
///
/// Defines Umbraco built-in property editor aliases.
///
@@ -79,6 +96,11 @@ namespace Umbraco.Core
///
public const string MediaPicker = "Umbraco.MediaPicker";
+ ///
+ /// Multiple Media Picker.
+ ///
+ public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker";
+
///
/// Member Picker.
///
@@ -186,6 +208,24 @@ namespace Umbraco.Core
/// Must be a valid value.
public const string DataValueType = "umbracoDataValueType";
}
+
+ ///
+ /// Defines Umbraco's built-in property editor groups.
+ ///
+ public static class Groups
+ {
+ public const string Common = "Common";
+
+ public const string Lists = "Lists";
+
+ public const string Media = "Media";
+
+ public const string People = "People";
+
+ public const string Pickers = "Pickers";
+
+ public const string RichContent = "Rich Content";
+ }
}
}
}
diff --git a/src/Umbraco.Core/DateTimeExtensions.cs b/src/Umbraco.Core/DateTimeExtensions.cs
index 66b788f9c8..f665aaa8ed 100644
--- a/src/Umbraco.Core/DateTimeExtensions.cs
+++ b/src/Umbraco.Core/DateTimeExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Text;
@@ -15,7 +16,7 @@ namespace Umbraco.Core
///
public static string ToIsoString(this DateTime dt)
{
- return dt.ToString("yyyy-MM-dd HH:mm:ss");
+ return dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
}
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs
index 3719bb0750..59f5937874 100644
--- a/src/Umbraco.Core/EnumerableExtensions.cs
+++ b/src/Umbraco.Core/EnumerableExtensions.cs
@@ -10,6 +10,8 @@ namespace Umbraco.Core
///
public static class EnumerableExtensions
{
+ internal static bool IsCollectionEmpty(this IReadOnlyCollection list) => list == null || list.Count == 0;
+
internal static bool HasDuplicates(this IEnumerable items, bool includeNull)
{
var hs = new HashSet();
@@ -112,7 +114,6 @@ namespace Umbraco.Core
}
}
-
///
/// Returns true if all items in the other collection exist in this collection
///
diff --git a/src/Umbraco.Core/Exceptions/PanicException.cs b/src/Umbraco.Core/Exceptions/PanicException.cs
new file mode 100644
index 0000000000..4d41cafc65
--- /dev/null
+++ b/src/Umbraco.Core/Exceptions/PanicException.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Exceptions
+{
+ ///
+ /// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen
+ ///
+ [Serializable]
+ internal class PanicException : Exception
+ {
+ public PanicException()
+ {
+ }
+
+ public PanicException(string message) : base(message)
+ {
+ }
+
+ public PanicException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs
index 2f33d82bdc..d6fb63b0a1 100644
--- a/src/Umbraco.Core/IO/SystemDirectories.cs
+++ b/src/Umbraco.Core/IO/SystemDirectories.cs
@@ -15,6 +15,8 @@ namespace Umbraco.Core.IO
public static string TempFileUploads => TempData + "/FileUploads";
+ public static string TempImageUploads => TempFileUploads + "/rte";
+
public static string Install => "~/install";
public static string AppCode => "~/App_Code";
diff --git a/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs b/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs
index b39a3f38df..dbdd7842ba 100644
--- a/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs
+++ b/src/Umbraco.Core/Logging/Viewer/ILogViewer.cs
@@ -42,6 +42,12 @@ namespace Umbraco.Core.Logging.Viewer
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
+ ///
+ /// Gets the current Serilog minimum log level
+ ///
+ ///
+ string GetLogLevel();
+
///
/// Returns the collection of logs
///
diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs
index acb2f5dbf9..607c20e601 100644
--- a/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs
+++ b/src/Umbraco.Core/Logging/Viewer/LogViewerSourceBase.cs
@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Xml;
using Newtonsoft.Json;
+using Serilog;
using Serilog.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
+using Formatting = Newtonsoft.Json.Formatting;
namespace Umbraco.Core.Logging.Viewer
{
@@ -89,6 +92,16 @@ namespace Umbraco.Core.Logging.Viewer
return errorCounter.Count;
}
+ ///
+ /// Get the Serilog minimum-level value from the config file.
+ ///
+ ///
+ public string GetLogLevel()
+ {
+ var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast().Where(Log.Logger.IsEnabled)?.Min() ?? null;
+ return logLevel?.ToString() ?? "";
+ }
+
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
{
var counter = new CountingFilter();
diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs
index ca875c2167..d1012fb669 100644
--- a/src/Umbraco.Core/MainDom.cs
+++ b/src/Umbraco.Core/MainDom.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Web.Hosting;
@@ -113,7 +114,7 @@ namespace Umbraco.Core
lock (_locko)
{
- _logger.Debug("Signaled {Signaled} ({SignalSource})", _signaled ? "(again)" : string.Empty, source);
+ _logger.Debug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source);
if (_signaled) return;
if (_isMainDom == false) return; // probably not needed
_signaled = true;
@@ -171,6 +172,7 @@ namespace Umbraco.Core
// if more than 1 instance reach that point, one will get the lock
// and the other one will timeout, which is accepted
+ //TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset?
_asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds);
_isMainDom = true;
@@ -181,6 +183,9 @@ namespace Umbraco.Core
// which is accepted
_signal.Reset();
+
+ //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
+
_signal.WaitOneAsync()
.ContinueWith(_ => OnSignal("signal"));
diff --git a/src/Umbraco.Core/Manifest/IManifestFilter.cs b/src/Umbraco.Core/Manifest/IManifestFilter.cs
new file mode 100644
index 0000000000..505f13d385
--- /dev/null
+++ b/src/Umbraco.Core/Manifest/IManifestFilter.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Manifest
+{
+ ///
+ /// Provides filtering for package manifests.
+ ///
+ public interface IManifestFilter
+ {
+ ///
+ /// Filters package manifests.
+ ///
+ /// The package manifests.
+ ///
+ /// It is possible to remove, change, or add manifests.
+ ///
+ void Filter(List manifests);
+ }
+}
diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs
new file mode 100644
index 0000000000..febdb7e356
--- /dev/null
+++ b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.Manifest
+{
+ ///
+ /// Contains the manifest filters.
+ ///
+ public class ManifestFilterCollection : BuilderCollectionBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ManifestFilterCollection(IEnumerable items)
+ : base(items)
+ { }
+
+ ///
+ /// Filters package manifests.
+ ///
+ /// The package manifests.
+ public void Filter(List manifests)
+ {
+ foreach (var filter in this)
+ filter.Filter(manifests);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs
new file mode 100644
index 0000000000..4d98700f93
--- /dev/null
+++ b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs
@@ -0,0 +1,12 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.Manifest
+{
+ public class ManifestFilterCollectionBuilder : OrderedCollectionBuilderBase
+ {
+ protected override ManifestFilterCollectionBuilder This => this;
+
+ // do NOT cache this, it's only used once
+ protected override Lifetime CollectionLifetime => Lifetime.Transient;
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs
index dc40cd90a2..efd9e92b1f 100644
--- a/src/Umbraco.Core/Manifest/ManifestParser.cs
+++ b/src/Umbraco.Core/Manifest/ManifestParser.cs
@@ -22,24 +22,26 @@ namespace Umbraco.Core.Manifest
private readonly IAppPolicyCache _cache;
private readonly ILogger _logger;
private readonly ManifestValueValidatorCollection _validators;
+ private readonly ManifestFilterCollection _filters;
private string _path;
///
/// Initializes a new instance of the class.
///
- public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ILogger logger)
- : this(appCaches, validators, "~/App_Plugins", logger)
+ public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, ILogger logger)
+ : this(appCaches, validators, filters, "~/App_Plugins", logger)
{ }
///
/// Initializes a new instance of the class.
///
- private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, string path, ILogger logger)
+ private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string path, ILogger logger)
{
if (appCaches == null) throw new ArgumentNullException(nameof(appCaches));
_cache = appCaches.RuntimeCache;
_validators = validators ?? throw new ArgumentNullException(nameof(validators));
+ _filters = filters ?? throw new ArgumentNullException(nameof(filters));
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path));
Path = path;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -78,6 +80,7 @@ namespace Umbraco.Core.Manifest
if (string.IsNullOrWhiteSpace(text))
continue;
var manifest = ParseManifest(text);
+ manifest.Source = path;
manifests.Add(manifest);
}
catch (Exception e)
@@ -86,6 +89,8 @@ namespace Umbraco.Core.Manifest
}
}
+ _filters.Filter(manifests);
+
return manifests;
}
diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs
index 475ee8a7f8..e50eb69467 100644
--- a/src/Umbraco.Core/Manifest/PackageManifest.cs
+++ b/src/Umbraco.Core/Manifest/PackageManifest.cs
@@ -9,6 +9,16 @@ namespace Umbraco.Core.Manifest
///
public class PackageManifest
{
+ ///
+ /// Gets the source path of the manifest.
+ ///
+ ///
+ /// Gets the full absolute file path of the manifest,
+ /// using system directory separators.
+ ///
+ [JsonIgnore]
+ public string Source { get; set; }
+
///
/// Gets or sets the scripts listed in the manifest.
///
diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
index 8915ebcf74..e41a40e3d9 100644
--- a/src/Umbraco.Core/Mapping/UmbracoMapper.cs
+++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Mapping
{
@@ -231,10 +232,10 @@ namespace Umbraco.Core.Mapping
if (ctor != null && map != null)
{
// register (for next time) and do it now (for this time)
- object NCtor(object s, MapperContext c) => MapEnumerableInternal((IEnumerable) s, targetGenericArg, ctor, map, c);
+ object NCtor(object s, MapperContext c) => MapEnumerableInternal((IEnumerable)s, targetGenericArg, ctor, map, c);
DefineCtors(sourceType)[targetType] = NCtor;
DefineMaps(sourceType)[targetType] = Identity;
- return (TTarget) NCtor(source, context);
+ return (TTarget)NCtor(source, context);
}
throw new InvalidOperationException($"Don't know how to map {sourceGenericArg.FullName} to {targetGenericArg.FullName}, so don't know how to map {sourceType.FullName} to {targetType.FullName}.");
@@ -259,13 +260,13 @@ namespace Umbraco.Core.Mapping
if (typeof(TTarget).IsArray)
{
var elementType = typeof(TTarget).GetElementType();
- if (elementType == null) throw new Exception("panic");
+ if (elementType == null) throw new PanicException("elementType == null which should never occur");
var targetArray = Array.CreateInstance(elementType, targetList.Count);
targetList.CopyTo(targetArray, 0);
target = targetArray;
}
- return (TTarget) target;
+ return (TTarget)target;
}
///
@@ -342,7 +343,17 @@ namespace Umbraco.Core.Mapping
if (ctor == null) return null;
- _ctors[sourceType] = sourceCtor;
+ if (_ctors.ContainsKey(sourceType))
+ {
+ foreach (var c in sourceCtor)
+ {
+ if (!_ctors[sourceType].TryGetValue(c.Key, out _))
+ _ctors[sourceType].Add(c.Key, c.Value);
+ }
+ }
+ else
+ _ctors[sourceType] = sourceCtor;
+
return ctor;
}
@@ -367,7 +378,17 @@ namespace Umbraco.Core.Mapping
if (map == null) return null;
- _maps[sourceType] = sourceMap;
+ if (_maps.ContainsKey(sourceType))
+ {
+ foreach (var m in sourceMap)
+ {
+ if (!_maps[sourceType].TryGetValue(m.Key, out _))
+ _maps[sourceType].Add(m.Key, m.Value);
+ }
+ }
+ else
+ _maps[sourceType] = sourceMap;
+
return map;
}
@@ -382,7 +403,7 @@ namespace Umbraco.Core.Mapping
{
if (type.IsArray) return type.GetElementType();
if (type.IsGenericType) return type.GenericTypeArguments[0];
- throw new Exception("panic");
+ throw new PanicException($"Could not get enumerable or array type from {type}");
}
///
diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs
index 9b13457b76..df74bf7c87 100644
--- a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs
+++ b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs
@@ -1,5 +1,7 @@
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
using NPoco;
+using Umbraco.Core;
using Umbraco.Core.Migrations.Expressions.Common;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -27,31 +29,57 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes
{
_context.BuildingExpression = false;
+ //get a list of all constraints - this will include all PK, FK and unique constraints
+ var tableConstraints = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
+
+ //get a list of defined indexes - this will include all indexes, unique indexes and unique constraint indexes
+ var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
+
+ var uniqueConstraintNames = tableConstraints.Where(x => !x.Item2.InvariantStartsWith("PK_") && !x.Item2.InvariantStartsWith("FK_")).Select(x => x.Item2);
+ var indexNames = indexes.Select(x => x.IndexName).ToList();
+
// drop keys
if (DeleteLocal || DeleteForeign)
{
// table, constraint
- var tableKeys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
+
if (DeleteForeign)
{
- foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_")))
+ //In some cases not all FK's are prefixed with "FK" :/ mostly with old upgraded databases so we need to check if it's either:
+ // * starts with FK OR
+ // * doesn't start with PK_ and doesn't exist in the list of indexes
+
+ foreach (var key in tableConstraints.Where(x => x.Item1 == TableName
+ && (x.Item2.InvariantStartsWith("FK_") || (!x.Item2.InvariantStartsWith("PK_") && !indexNames.InvariantContains(x.Item2)))))
+ {
Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do();
+ }
+
}
if (DeleteLocal)
{
- foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_")))
+ foreach (var key in tableConstraints.Where(x => x.Item1 == TableName && x.Item2.InvariantStartsWith("PK_")))
Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do();
- // note: we do *not* delete the DEFAULT constraints
+ // note: we do *not* delete the DEFAULT constraints and if we wanted to we'd have to deal with that in interesting ways
+ // since SQL server has a specific way to handle that, see SqlServerSyntaxProvider.GetDefaultConstraintsPerColumn
}
}
// drop indexes
if (DeleteLocal)
- {
- var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
+ {
foreach (var index in indexes.Where(x => x.TableName == TableName))
- Delete.Index(index.IndexName).OnTable(index.TableName).Do();
+ {
+ //if this is a unique constraint we need to drop the constraint, else drop the index
+ //to figure this out, the index must be tagged as unique and it must exist in the tableConstraints
+
+ if (index.IsUnique && uniqueConstraintNames.InvariantContains(index.IndexName))
+ Delete.UniqueConstraint(index.IndexName).FromTable(index.TableName).Do();
+ else
+ Delete.Index(index.IndexName).OnTable(index.TableName).Do();
+ }
+
}
}
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
index 1de983636b..7d5c05d584 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
@@ -101,33 +101,33 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = Constants.ObjectTypes.SystemRoot, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.ContentRecycleBin, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.MediaRecycleBin, CreateDate = DateTime.Now });
- InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, "f0bc4bfb-b499-40d6-ba86-058885a5178c", "Label");
- InsertDataTypeNodeDto(Constants.DataTypes.LabelInt, 36, "8e7f995c-bd81-4627-9932-c40e568ec788", "Label (integer)");
- InsertDataTypeNodeDto(Constants.DataTypes.LabelBigint, 36, "930861bf-e262-4ead-a704-f99453565708", "Label (bigint)");
- InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, "0e9794eb-f9b5-4f20-a788-93acd233a7e4", "Label (datetime)");
- InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, "a97cec69-9b71-4c30-8b12-ec398860d7e8", "Label (time)");
- InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, "8f1ef1e1-9de4-40d3-a072-6673f631ca64", "Label (decimal)");
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _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.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.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 });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, Constants.DataTypes.Guids.LabelString, "Label (string)");
+ InsertDataTypeNodeDto(Constants.DataTypes.LabelInt, 36, Constants.DataTypes.Guids.LabelInt, "Label (integer)");
+ InsertDataTypeNodeDto(Constants.DataTypes.LabelBigint, 36, Constants.DataTypes.Guids.LabelBigInt, "Label (bigint)");
+ InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, Constants.DataTypes.Guids.LabelDateTime, "Label (datetime)");
+ InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, Constants.DataTypes.Guids.LabelTime, "Label (time)");
+ InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, Constants.DataTypes.Guids.LabelDecimal, "Label (decimal)");
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = Constants.DataTypes.Guids.UploadGuid, Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textarea, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textarea}", SortOrder = 33, UniqueId = Constants.DataTypes.Guids.TextareaGuid, Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textbox, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textbox}", SortOrder = 32, UniqueId = Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.RichtextEditor, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.RichtextEditor}", SortOrder = 4, UniqueId = Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _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 = Constants.DataTypes.Guids.NumericGuid, Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Boolean, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Boolean}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.CheckboxGuid, 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 = Constants.DataTypes.Guids.CheckboxListGuid, Text = "Checkbox list", 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 = Constants.DataTypes.Guids.DropdownGuid, 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 = Constants.DataTypes.Guids.DatePickerGuid, 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 = Constants.DataTypes.Guids.RadioboxGuid, Text = "Radiobox", 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 = Constants.DataTypes.Guids.DropdownMultipleGuid, 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 = Constants.DataTypes.Guids.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DateTime, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DateTime}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DatePickerWithTimeGuid, 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 = Constants.DataTypes.Guids.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewMediaGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Tags, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Tags}", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.ImageCropper, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.ImageCropper}", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = Constants.ObjectTypes.MemberType, CreateDate = DateTime.Now });
//New UDI pickers with newer Ids
@@ -155,10 +155,10 @@ namespace Umbraco.Core.Migrations.Install
private void CreateContentTypeData()
{
- _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
- _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
- _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
- _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user", Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = Constants.Icons.MediaFolder, Thumbnail = Constants.Icons.MediaFolder, IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = Constants.Icons.MediaImage, Thumbnail = Constants.Icons.MediaImage, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = Constants.Icons.MediaFile, Thumbnail = Constants.Icons.MediaFile, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = Constants.Icons.Member, Thumbnail = Constants.Icons.Member, Variations = (byte) ContentVariation.Nothing });
}
private void CreateUserData()
@@ -190,6 +190,7 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings });
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users });
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms });
+ _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Translation });
_database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content });
@@ -210,19 +211,19 @@ 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 = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = Constants.DataTypes.ImageCropper, 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 = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = Constants.DataTypes.Upload, 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 = "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 = 28, UniqueId = 28.ToGuid(), DataTypeId = Constants.DataTypes.Textarea, 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 });
- _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
- _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
@@ -266,30 +267,30 @@ namespace Umbraco.Core.Migrations.Install
const string layouts = "[" + cardLayout + "," + listLayout + "]";
// TODO: Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors.
- _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -49, EditorAlias = Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" });
+ _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Boolean, EditorAlias = Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -51, EditorAlias = Constants.PropertyEditors.Aliases.Integer, DbType = "Integer" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -87, EditorAlias = Constants.PropertyEditors.Aliases.TinyMce, DbType = "Ntext",
Configuration = "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}" });
- _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" });
+ _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Textbox, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" });
+ _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Textarea, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" });
+ _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Upload, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" });
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 = Constants.DataTypes.DateTime, 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 = "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",
+ _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Tags, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext",
Configuration = "{\"group\":\"default\", \"storageType\":\"Json\"}"
});
- _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1043, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" });
+ _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.ImageCropper, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar",
Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMediaListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar",
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index 3a3c9cffc5..cf9e0f7b67 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -74,6 +74,11 @@ namespace Umbraco.Core.Migrations.Upgrade
throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}."
+ $" Please upgrade first to at least {minVersion}.");
+ // Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes,
+ // and we don't want users to workaround my putting in version 7.14.0 them self.
+ if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16))
+ return GetInitState(minVersion);
+
// initial state is eg "{init-7.14.0}"
return GetInitState(currentVersion);
}
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 2f20f01728..309f8acbc3 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
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Linq;
using NPoco;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
@@ -71,10 +72,21 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
// flip known property types
- var intPropertyTypes = new[] { 7, 8, 29 };
- var bigintPropertyTypes = new[] { 9, 26 };
- var dtPropertyTypes = new[] { 32, 33, 34 };
+ var labelPropertyTypes = Database.Fetch(Sql()
+ .Select(x => x.Id, x => x.Alias)
+ .From()
+ .Where(x => x.DataTypeId == Constants.DataTypes.LabelString)
+ );
+ var intPropertyAliases = new[] { Constants.Conventions.Media.Width, Constants.Conventions.Media.Height, Constants.Conventions.Member.FailedPasswordAttempts };
+ var bigintPropertyAliases = new[] { Constants.Conventions.Media.Bytes };
+ var dtPropertyAliases = new[] { Constants.Conventions.Member.LastLockoutDate, Constants.Conventions.Member.LastLoginDate, Constants.Conventions.Member.LastPasswordChangeDate };
+
+ var intPropertyTypes = labelPropertyTypes.Where(pt => intPropertyAliases.Contains(pt.Alias)).Select(pt => pt.Id).ToArray();
+ var bigintPropertyTypes = labelPropertyTypes.Where(pt => bigintPropertyAliases.Contains(pt.Alias)).Select(pt => pt.Id).ToArray();
+ var dtPropertyTypes = labelPropertyTypes.Where(pt => dtPropertyAliases.Contains(pt.Alias)).Select(pt => pt.Id).ToArray();
+
+ Database.Execute(Sql().Update(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelInt)).WhereIn(x => x.Id, intPropertyTypes));
Database.Execute(Sql().Update(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelInt)).WhereIn(x => x.Id, intPropertyTypes));
Database.Execute(Sql().Update(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelBigint)).WhereIn(x => x.Id, bigintPropertyTypes));
Database.Execute(Sql().Update(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelDateTime)).WhereIn(x => x.Id, dtPropertyTypes));
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs
index ed1c08f0f8..1956876402 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs
@@ -19,8 +19,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
var sqlDataTypes = Sql()
.Select()
.From()
- .Where(x => x.EditorAlias == "Umbraco.RelatedLinks"
- || x.EditorAlias == "Umbraco.RelatedLinks2");
+ .Where(x => x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks
+ || x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2);
var dataTypes = Database.Fetch(sqlDataTypes);
var dataTypeIds = dataTypes.Select(x => x.NodeId).ToList();
@@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
var properties = Database.Fetch(sqlPropertyData);
// Create a Multi URL Picker datatype for the converted RelatedLinks data
-
+
foreach (var property in properties)
{
- var value = property.Value.ToString();
+ var value = property.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
continue;
@@ -72,6 +72,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
var sqlNodeData = Sql()
.Select()
+ .From()
.Where(x => x.NodeId == intId);
var node = Database.Fetch(sqlNodeData).FirstOrDefault();
@@ -89,7 +90,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
Name = relatedLink.Caption,
Target = relatedLink.NewWindow ? "_blank" : null,
Udi = udi,
- // Should only have a URL if it's an external link otherwise it wil be a UDI
+ // Should only have a URL if it's an external link otherwise it wil be a UDI
Url = relatedLink.IsInternal == false ? relatedLink.Link : null
};
@@ -103,7 +104,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
Database.Update(property);
}
-
+
}
}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs
index 438b02385b..7b2daa99ef 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Umbraco.Core.Composing;
@@ -18,6 +19,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
private readonly PropertyEditorCollection _propertyEditors;
private readonly ILogger _logger;
+ private static readonly ISet LegacyAliases = new HashSet()
+ {
+ Constants.PropertyEditors.Legacy.Aliases.Date,
+ Constants.PropertyEditors.Legacy.Aliases.Textbox,
+ Constants.PropertyEditors.Legacy.Aliases.ContentPicker2,
+ Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
+ Constants.PropertyEditors.Legacy.Aliases.MemberPicker2,
+ Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2,
+ Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple,
+ Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2,
+ };
+
public DataTypeMigration(IMigrationContext context, PreValueMigratorCollection preValueMigrators, PropertyEditorCollection propertyEditors, ILogger logger)
: base(context)
{
@@ -70,16 +83,23 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
var newAlias = migrator.GetNewAlias(dataType.EditorAlias);
if (newAlias == null)
{
- _logger.Warn("Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
- + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
- dataType.NodeId, dataType.EditorAlias);
+ if (!LegacyAliases.Contains(dataType.EditorAlias))
+ {
+ _logger.Warn(
+ "Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
+ + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
+ dataType.NodeId, dataType.EditorAlias);
+ }
}
else if (!_propertyEditors.TryGet(newAlias, out var propertyEditor))
{
- _logger.Warn("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
- + " because no property editor with that alias was found."
- + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
- dataType.NodeId, newAlias, dataType.EditorAlias);
+ if (!LegacyAliases.Contains(newAlias))
+ {
+ _logger.Warn("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
+ + " because no property editor with that alias was found."
+ + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
+ dataType.NodeId, newAlias, dataType.EditorAlias);
+ }
}
else
{
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs
index 2e341ad091..f445742aa9 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs
@@ -3,7 +3,7 @@
class ContentPickerPreValueMigrator : DefaultPreValueMigrator
{
public override bool CanMigrate(string editorAlias)
- => editorAlias == "Umbraco.ContentPicker2";
+ => editorAlias == Constants.PropertyEditors.Legacy.Aliases.ContentPicker2;
public override string GetNewAlias(string editorAlias)
=> null;
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs
new file mode 100644
index 0000000000..35ca574bab
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.PropertyEditors;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
+{
+ class DropDownFlexiblePreValueMigrator : IPreValueMigrator
+ {
+ public bool CanMigrate(string editorAlias)
+ => editorAlias == "Umbraco.DropDown.Flexible";
+
+ public virtual string GetNewAlias(string editorAlias)
+ => null;
+
+ public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues)
+ {
+ var config = new DropDownFlexibleConfiguration();
+ foreach (var preValue in preValues.Values)
+ {
+ if (preValue.Alias == "multiple")
+ {
+ config.Multiple = (preValue.Value == "1");
+ }
+ else
+ {
+ config.Items.Add(new ValueListConfiguration.ValueListItem { Id = preValue.Id, Value = preValue.Value });
+ }
+ }
+ return config;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MarkdownEditorPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MarkdownEditorPreValueMigrator.cs
new file mode 100644
index 0000000000..87a1fd6504
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MarkdownEditorPreValueMigrator.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
+{
+ class MarkdownEditorPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase
+ {
+ public override bool CanMigrate(string editorAlias)
+ => editorAlias == Constants.PropertyEditors.Aliases.MarkdownEditor;
+
+ protected override object GetPreValueValue(PreValueDto preValue)
+ {
+ if (preValue.Alias == "preview")
+ return preValue.Value == "1";
+
+ return base.GetPreValueValue(preValue);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs
index a46b1eefb7..922d886595 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs
@@ -6,15 +6,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
private readonly string[] _editors =
{
- "Umbraco.MediaPicker2",
- "Umbraco.MediaPicker"
+ Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
+ Constants.PropertyEditors.Aliases.MediaPicker
};
public override bool CanMigrate(string editorAlias)
=> _editors.Contains(editorAlias);
public override string GetNewAlias(string editorAlias)
- => "Umbraco.MediaPicker";
+ => Constants.PropertyEditors.Aliases.MediaPicker;
// you wish - but MediaPickerConfiguration lives in Umbraco.Web
/*
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs
index 9e2dec9b70..db9021d653 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs
@@ -2,24 +2,26 @@
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
- [RuntimeLevel(MinLevel = RuntimeLevel.Upgrade, MaxLevel = RuntimeLevel.Upgrade)] // only on upgrades
- public class PreValueMigratorComposer : ICoreComposer
+[RuntimeLevel(MinLevel = RuntimeLevel.Upgrade, MaxLevel = RuntimeLevel.Upgrade)] // only on upgrades
+public class PreValueMigratorComposer : ICoreComposer
+{
+ public void Compose(Composition composition)
{
- public void Compose(Composition composition)
- {
- // do NOT add DefaultPreValueMigrator to this list!
- // it will be automatically used if nothing matches
+ // do NOT add DefaultPreValueMigrator to this list!
+ // it will be automatically used if nothing matches
- composition.WithCollectionBuilder()
- .Append()
- .Append()
- .Append()
- .Append()
- .Append()
- .Append()
- .Append()
- .Append()
- .Append();
- }
+ composition.WithCollectionBuilder()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append();
}
}
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs
index c04e7c8fda..89a71fdaf4 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
@@ -20,7 +21,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
case "Umbraco.NoEdit":
return Constants.PropertyEditors.Aliases.Label;
default:
- throw new Exception("panic");
+ throw new PanicException($"The alias {editorAlias} is not supported");
}
}
}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs
index 7249ebd6ec..07fefc8e85 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs
@@ -9,6 +9,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
private readonly string[] _editors =
{
"Umbraco.RadioButtonList",
+ "Umbraco.CheckBoxList",
"Umbraco.DropDown",
"Umbraco.DropdownlistPublishingKeys",
"Umbraco.DropDownMultiple",
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
index a434b9f1c1..0d451e8460 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
public override void Migrate()
{
- var dataTypes = GetDataTypes("Umbraco.Date");
+ var dataTypes = GetDataTypes(Constants.PropertyEditors.Legacy.Aliases.Date);
foreach (var dataType in dataTypes)
{
@@ -25,6 +25,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
config = (DateTimeConfiguration) new CustomDateTimeConfigurationEditor().FromDatabase(
dataType.Configuration);
+
+ // If the Umbraco.Date type is the default from V7 and it has never been updated, then the
+ // configuration is empty, and the format stuff is handled by in JS by moment.js. - We can't do that
+ // after the migration, so we force the format to the default from V7.
+ if (string.IsNullOrEmpty(dataType.Configuration))
+ {
+ config.Format = "YYYY-MM-DD";
+ };
}
catch (Exception ex)
{
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs
index 064ffc7228..89a8f010ec 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs
@@ -12,12 +12,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
public override void Migrate()
{
- RenameDataType(Constants.PropertyEditors.Aliases.ContentPicker + "2", Constants.PropertyEditors.Aliases.ContentPicker);
- RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker);
- RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker);
- RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
- RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false);
- RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false);
+ RenameDataType(Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, Constants.PropertyEditors.Aliases.ContentPicker);
+ RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, Constants.PropertyEditors.Aliases.MediaPicker);
+ RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, Constants.PropertyEditors.Aliases.MemberPicker);
+ RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
+ RenameDataType(Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, Constants.PropertyEditors.Aliases.TextArea, false);
+ RenameDataType(Constants.PropertyEditors.Legacy.Aliases.Textbox, Constants.PropertyEditors.Aliases.TextBox, false);
}
private void RenameDataType(string fromAlias, string toAlias, bool checkCollision = true)
@@ -30,7 +30,20 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
.Where(x => x.EditorAlias == toAlias));
if (oldCount > 0)
- throw new InvalidOperationException($"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used.");
+ {
+ // If we throw it means that the upgrade will exit and cannot continue.
+ // This will occur if a v7 site has the old "Obsolete" property editors that are already named with the `toAlias` name.
+ // TODO: We should have an additional upgrade step when going from 7 -> 8 like we did with 6 -> 7 that shows a compatibility report,
+ // this would include this check and then we can provide users with information on what they should do (i.e. before upgrading to v8 they will
+ // need to migrate these old obsolete editors to non-obsolete editors)
+
+ throw new InvalidOperationException(
+ $"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used." +
+ $"This is generally because when upgrading from a v7 to v8 site, the v7 site contains Data Types that reference old and already Obsolete " +
+ $"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " +
+ $"to the non-obsolete v7 property editors of the same type.");
+ }
+
}
Database.Execute(Sql()
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs
index bd08b53877..58ec0e30c2 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs
@@ -15,21 +15,17 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
//special trick to add the column without constraints and return the sql to add them later
AddColumn("macroType", out var sqls1);
- //now we need to update the new column with some values because this column doesn't allow NULL values
- Update.Table(Constants.DatabaseSchema.Tables.Macro).Set(new { macroType = (int)MacroTypes.Unknown}).AllRows().Do();
- //now apply constraints (NOT NULL) to new table
- foreach (var sql in sqls1) Execute.Sql(sql).Do();
-
- //special trick to add the column without constraints and return the sql to add them later
AddColumn("macroSource", out var sqls2);
- //populate the new macroSource column with legacy data
- Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT IS NOT NULL").Do();
- Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly IS NOT NULL").Do();
- Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType IS NOT NULL").Do();
- Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython IS NOT NULL").Do();
+ //populate the new columns with legacy data
+ Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = '', macroType = {(int)MacroTypes.Unknown}").Do();
+ Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT != '' AND macroXSLT IS NOT NULL").Do();
+ Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly != '' AND macroScriptAssembly IS NOT NULL").Do();
+ Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType != '' AND macroScriptType IS NOT NULL").Do();
+ Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython != '' AND macroPython IS NOT NULL").Do();
//now apply constraints (NOT NULL) to new table
+ foreach (var sql in sqls1) Execute.Sql(sql).Do();
foreach (var sql in sqls2) Execute.Sql(sql).Do();
//now remove these old columns
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs
index 64ac20d175..9026f15fc1 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs
@@ -30,6 +30,7 @@
Database.Execute("set identity_insert umbracoUser off;");
Database.Execute("update umbracoUser2UserGroup set userId=-1 where userId=0;");
+ Database.Execute("update umbracoUser2NodeNotify set userId=-1 where userId=0;");
Database.Execute("update umbracoNode set nodeUser=-1 where nodeUser=0;");
Database.Execute("update umbracoUserLogin set userId=-1 where userId=0;");
Database.Execute($"update {Constants.DatabaseSchema.Tables.ContentVersion} set userId=-1 where userId=0;");
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs
index 70dbe3d29e..2f7ffe8679 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs
@@ -14,11 +14,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
public override void Migrate()
{
//Get anything currently scheduled
- var scheduleSql = new Sql()
- .Select("nodeId", "releaseDate", "expireDate")
+ var releaseSql = new Sql()
+ .Select("nodeId", "releaseDate")
.From("umbracoDocument")
- .Where("releaseDate IS NOT NULL OR expireDate IS NOT NULL");
- var schedules = Database.Dictionary (scheduleSql);
+ .Where("releaseDate IS NOT NULL");
+ var releases = Database.Dictionary (releaseSql);
+
+ var expireSql = new Sql()
+ .Select("nodeId", "expireDate")
+ .From("umbracoDocument")
+ .Where("expireDate IS NOT NULL");
+ var expires = Database.Dictionary(expireSql);
+
//drop old cols
Delete.Column("releaseDate").FromTable("umbracoDocument").Do();
@@ -27,20 +34,25 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
Create.Table(true).Do();
//migrate the schedule
- foreach(var s in schedules)
+ foreach(var s in releases)
{
- var date = s.Value.releaseDate;
+ var date = s.Value;
var action = ContentScheduleAction.Release.ToString();
- if (!date.HasValue)
- {
- date = s.Value.expireDate;
- action = ContentScheduleAction.Expire.ToString();
- }
Insert.IntoTable(ContentScheduleDto.TableName)
- .Row(new { nodeId = s.Key, date = date.Value, action = action })
+ .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action })
.Do();
}
+
+ foreach (var s in expires)
+ {
+ var date = s.Value;
+ var action = ContentScheduleAction.Expire.ToString();
+
+ Insert.IntoTable(ContentScheduleDto.TableName)
+ .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action })
+ .Do();
+ }
}
}
}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs
index bf048bf2bd..b68aa23d78 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
@@ -36,6 +37,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
var properties = Database.Fetch(sqlPropertyData);
+ var exceptions = new List();
foreach (var property in properties)
{
var value = property.TextValue;
@@ -43,19 +45,34 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid)
{
- var obj = JsonConvert.DeserializeObject(value);
- var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
-
- foreach (var control in allControls.SelectMany(c => c))
+ try
{
- var controlValue = control["value"];
- if (controlValue.Type == JTokenType.String)
+ var obj = JsonConvert.DeserializeObject(value);
+ var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
+
+ foreach (var control in allControls.SelectMany(c => c).OfType())
{
- control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value());
+ var controlValue = control["value"];
+ if (controlValue?.Type == JTokenType.String)
+ {
+ control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value());
+ }
}
+
+ property.TextValue = JsonConvert.SerializeObject(obj);
+ }
+ catch (JsonException e)
+ {
+ exceptions.Add(new InvalidOperationException(
+ "Cannot deserialize the value as json. This can be because the property editor " +
+ "type is changed from another type into a grid. Old versions of the value in this " +
+ "property can have the structure from the old property editor type. This needs to be " +
+ "changed manually before updating the database.\n" +
+ $"Property info: Id = {property.Id}, LanguageId = {property.LanguageId}, VersionId = {property.VersionId}, Value = {property.Value}"
+ , e));
+ continue;
}
- property.TextValue = JsonConvert.SerializeObject(obj);
}
else
{
@@ -65,6 +82,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
Database.Update(property);
}
+
+ if (exceptions.Any())
+ {
+ throw new AggregateException("One or more errors related to unexpected data in grid values occurred.", exceptions);
+ }
+
Context.AddPostMigration();
}
diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
index bf7228ca47..f9efc60142 100644
--- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
@@ -222,7 +222,13 @@ namespace Umbraco.Core.Models
return true;
}
- public static void UnpublishCulture(this IContent content, string culture = "*")
+ ///
+ /// Returns false if the culture is already unpublished
+ ///
+ ///
+ ///
+ ///
+ public static bool UnpublishCulture(this IContent content, string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -230,16 +236,31 @@ namespace Umbraco.Core.Models
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
+
+ var keepProcessing = true;
+
+ if (culture == "*")
+ {
+ // all cultures
content.ClearPublishInfos();
- else // one single culture
- content.ClearPublishInfo(culture);
+ }
+ else
+ {
+ // one single culture
+ keepProcessing = content.ClearPublishInfo(culture);
+ }
+
- // property.PublishValues only publishes what is valid, variation-wise
- foreach (var property in content.Properties)
- property.UnpublishValues(culture);
+ if (keepProcessing)
+ {
+ // property.PublishValues only publishes what is valid, variation-wise
+ foreach (var property in content.Properties)
+ property.UnpublishValues(culture);
- content.PublishedState = PublishedState.Publishing;
+ content.PublishedState = PublishedState.Publishing;
+ }
+
+ return keepProcessing;
}
public static void ClearPublishInfos(this IContent content)
@@ -247,15 +268,24 @@ namespace Umbraco.Core.Models
content.PublishCultureInfos = null;
}
- public static void ClearPublishInfo(this IContent content, string culture)
+ ///
+ /// Returns false if the culture is already unpublished
+ ///
+ ///
+ ///
+ ///
+ public static bool 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);
+ var removed = content.PublishCultureInfos.Remove(culture);
+ if (removed)
+ {
+ // set the culture to be dirty - it's been modified
+ content.TouchCulture(culture);
+ }
+ return removed;
}
///
diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs
index df8a3caea8..f460edbde7 100644
--- a/src/Umbraco.Core/Models/DataTypeExtensions.cs
+++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs
@@ -35,5 +35,58 @@ namespace Umbraco.Core.Models
throw new InvalidCastException($"Cannot cast dataType configuration, of type {configuration.GetType().Name}, to {typeof(T).Name}.");
}
+
+ private static readonly ISet IdsOfBuildInDataTypes = new HashSet()
+ {
+ Constants.DataTypes.Guids.ContentPickerGuid,
+ Constants.DataTypes.Guids.MemberPickerGuid,
+ Constants.DataTypes.Guids.MediaPickerGuid,
+ Constants.DataTypes.Guids.MultipleMediaPickerGuid,
+ Constants.DataTypes.Guids.RelatedLinksGuid,
+ Constants.DataTypes.Guids.MemberGuid,
+ Constants.DataTypes.Guids.ImageCropperGuid,
+ Constants.DataTypes.Guids.TagsGuid,
+ Constants.DataTypes.Guids.ListViewContentGuid,
+ Constants.DataTypes.Guids.ListViewMediaGuid,
+ Constants.DataTypes.Guids.ListViewMembersGuid,
+ Constants.DataTypes.Guids.DatePickerWithTimeGuid,
+ Constants.DataTypes.Guids.ApprovedColorGuid,
+ Constants.DataTypes.Guids.DropdownMultipleGuid,
+ Constants.DataTypes.Guids.RadioboxGuid,
+ Constants.DataTypes.Guids.DatePickerGuid,
+ Constants.DataTypes.Guids.DropdownGuid,
+ Constants.DataTypes.Guids.CheckboxListGuid,
+ Constants.DataTypes.Guids.CheckboxGuid,
+ Constants.DataTypes.Guids.NumericGuid,
+ Constants.DataTypes.Guids.RichtextEditorGuid,
+ Constants.DataTypes.Guids.TextstringGuid,
+ Constants.DataTypes.Guids.TextareaGuid,
+ Constants.DataTypes.Guids.UploadGuid,
+ Constants.DataTypes.Guids.LabelStringGuid,
+ Constants.DataTypes.Guids.LabelDecimalGuid,
+ Constants.DataTypes.Guids.LabelDateTimeGuid,
+ Constants.DataTypes.Guids.LabelBigIntGuid,
+ Constants.DataTypes.Guids.LabelTimeGuid,
+ Constants.DataTypes.Guids.LabelDateTimeGuid,
+ };
+
+ ///
+ /// Returns true if this date type is build-in/default.
+ ///
+ /// The data type definition.
+ ///
+ internal static bool IsBuildInDataType(this IDataType dataType)
+ {
+ return IsBuildInDataType(dataType.Key);
+ }
+
+ ///
+ /// Returns true if this date type is build-in/default.
+ ///
+ internal static bool IsBuildInDataType(Guid key)
+ {
+ return IdsOfBuildInDataTypes.Contains(key);
+ }
+
}
}
diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs
index 43837fa776..cdb3ecebc5 100644
--- a/src/Umbraco.Core/Models/Entities/EntityBase.cs
+++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs
@@ -81,37 +81,7 @@ namespace Umbraco.Core.Models.Entities
_key = Guid.Empty;
_hasIdentity = false;
}
-
- ///
- /// Updates the entity when it is being saved for the first time.
- ///
- internal virtual void AddingEntity()
- {
- var now = DateTime.Now;
-
- // set the create and update dates, if not already set
- if (IsPropertyDirty("CreateDate") == false || _createDate == default)
- CreateDate = now;
- if (IsPropertyDirty("UpdateDate") == false || _updateDate == default)
- UpdateDate = now;
- }
-
- ///
- /// Updates the entity when it is being saved.
- ///
- internal virtual void UpdatingEntity()
- {
- var now = DateTime.Now;
-
- // just in case
- if (_createDate == default)
- CreateDate = now;
-
- // set the update date if not already set
- if (IsPropertyDirty("UpdateDate") == false || _updateDate == default)
- UpdateDate = now;
- }
-
+
public virtual bool Equals(EntityBase other)
{
return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other));
diff --git a/src/Umbraco.Core/Models/Entities/EntityExtensions.cs b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs
new file mode 100644
index 0000000000..2ee6a2d5ed
--- /dev/null
+++ b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Umbraco.Core.Models.Entities
+{
+ internal static class EntityExtensions
+ {
+ ///
+ /// Updates the entity when it is being saved.
+ ///
+ internal static void UpdatingEntity(this IEntity entity)
+ {
+ var now = DateTime.Now;
+
+ // just in case
+ if (entity.CreateDate == default)
+ {
+ entity.CreateDate = now;
+ }
+
+ // set the update date if not already set
+ if (entity.UpdateDate == default || (entity is ICanBeDirty canBeDirty && canBeDirty.IsPropertyDirty("UpdateDate") == false))
+ {
+ entity.UpdateDate = now;
+ }
+ }
+
+ ///
+ /// Updates the entity when it is being saved for the first time.
+ ///
+ internal static void AddingEntity(this IEntity entity)
+ {
+ var now = DateTime.Now;
+ var canBeDirty = entity as ICanBeDirty;
+
+ // set the create and update dates, if not already set
+ if (entity.CreateDate == default || canBeDirty?.IsPropertyDirty("CreateDate") == false)
+ {
+ entity.CreateDate = now;
+ }
+ if (entity.UpdateDate == default || canBeDirty?.IsPropertyDirty("UpdateDate") == false)
+ {
+ entity.UpdateDate = now;
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs
index 5f1fe6ed49..ed87c5f320 100644
--- a/src/Umbraco.Core/Models/IContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/IContentTypeBase.cs
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
PropertyGroupCollection PropertyGroups { get; set; }
///
- /// Gets all local property types belonging to a group, across all local property groups.
+ /// Gets all local property types all local property groups or ungrouped.
///
IEnumerable PropertyTypes { get; }
diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs
index 0e91065d56..b473a154f1 100644
--- a/src/Umbraco.Core/Models/Member.cs
+++ b/src/Umbraco.Core/Models/Member.cs
@@ -478,19 +478,6 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _providerUserKey, nameof(ProviderUserKey));
}
-
- ///
- /// Method to call when Entity is being saved
- ///
- /// Created date is set and a Unique key is assigned
- internal override void AddingEntity()
- {
- base.AddingEntity();
-
- if (ProviderUserKey == null)
- ProviderUserKey = Key;
- }
-
/* Internal experiment - only used for mapping queries.
* Adding these to have first level properties instead of the Properties collection.
*/
diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs
index 1ae5133f8d..fd23756acb 100644
--- a/src/Umbraco.Core/Models/PropertyType.cs
+++ b/src/Umbraco.Core/Models/PropertyType.cs
@@ -21,6 +21,7 @@ namespace Umbraco.Core.Models
private string _alias;
private string _description;
private int _dataTypeId;
+ private Guid _dataTypeKey;
private Lazy _propertyGroupId;
private string _propertyEditorAlias;
private ValueStorageType _valueStorageType;
@@ -115,7 +116,7 @@ namespace Umbraco.Core.Models
/// Gets of sets the alias of the property type.
///
[DataMember]
- public string Alias
+ public virtual string Alias
{
get => _alias;
set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, nameof(Alias));
@@ -141,6 +142,13 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId));
}
+ [DataMember]
+ public Guid DataTypeKey
+ {
+ get => _dataTypeKey;
+ set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey));
+ }
+
///
/// Gets or sets the alias of the property editor for this property type.
///
diff --git a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs
index 4027184f3c..0810f2207b 100644
--- a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs
@@ -13,6 +13,10 @@
///
/// Refreshes the factory.
///
+ ///
+ /// This will typically re-compiled models/classes into a new DLL that are used to populate the cache.
+ /// This is called prior to refreshing the cache.
+ ///
void Refresh();
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs
index 816bfdbb01..89009ac7b8 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs
@@ -30,6 +30,16 @@
/// Is used by constructor to create special property types.
IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations);
+ ///
+ /// Creates a core (non-user) published property type.
+ ///
+ /// The published content type owning the property.
+ /// The property type alias.
+ /// The datatype identifier.
+ /// The variations.
+ /// Is used by constructor to create special property types.
+ IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations);
+
///
/// Gets a published datatype.
///
diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
index 540abda2c5..318ccc916e 100644
--- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type;
var def = type.GetGenericTypeDefinition();
if (def == null)
- throw new InvalidOperationException("panic");
+ throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
return def.MakeGenericType(args);
@@ -114,7 +114,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type.FullName;
var def = type.GetGenericTypeDefinition();
if (def == null)
- throw new InvalidOperationException("panic");
+ throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
index 3b03cfc9ea..b11e991118 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
@@ -92,26 +92,26 @@ namespace Umbraco.Core.Models.PublishedContent
{
var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase);
- foreach ((var alias, (var dataTypeId, var editorAlias)) in BuiltinMemberProperties)
+ foreach (var (alias, dataTypeId) in BuiltinMemberProperties)
{
if (aliases.Contains(alias)) continue;
- propertyTypes.Add(factory.CreatePropertyType(this, alias, dataTypeId, ContentVariation.Nothing));
+ propertyTypes.Add(factory.CreateCorePropertyType(this, alias, dataTypeId, ContentVariation.Nothing));
}
}
// TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate!
- private static readonly Dictionary BuiltinMemberProperties = new Dictionary
+ private static readonly Dictionary BuiltinMemberProperties = new Dictionary
{
- { "Email", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
- { "Username", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
- { "PasswordQuestion", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
- { "Comments", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
- { "IsApproved", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) },
- { "IsLockedOut", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) },
- { "LastLockoutDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
- { "CreateDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
- { "LastLoginDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
- { "LastPasswordChangeDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
+ { "Email", Constants.DataTypes.Textbox },
+ { "Username", Constants.DataTypes.Textbox },
+ { "PasswordQuestion", Constants.DataTypes.Textbox },
+ { "Comments", Constants.DataTypes.Textbox },
+ { "IsApproved", Constants.DataTypes.Boolean },
+ { "IsLockedOut", Constants.DataTypes.Boolean },
+ { "LastLockoutDate", Constants.DataTypes.DateTime },
+ { "CreateDate", Constants.DataTypes.DateTime },
+ { "LastLoginDate", Constants.DataTypes.DateTime },
+ { "LastPasswordChangeDate", Constants.DataTypes.DateTime },
};
#region Content type
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs
index 17a15a2536..34094508c3 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs
@@ -61,6 +61,12 @@ namespace Umbraco.Core.Models.PublishedContent
return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this);
}
+ ///
+ public IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing)
+ {
+ return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, this);
+ }
+
///
/// This method is for tests and is not intended to be used directly from application code.
///
diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs
index 0f83cf78a4..cf7df4fb86 100644
--- a/src/Umbraco.Core/Models/UserExtensions.cs
+++ b/src/Umbraco.Core/Models/UserExtensions.cs
@@ -193,19 +193,6 @@ namespace Umbraco.Core.Models
return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
}
- internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess)
- {
- switch (recycleBinId)
- {
- case Constants.System.RecycleBinMedia:
- return ContentPermissionsHelper.IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess);
- case Constants.System.RecycleBinContent:
- return ContentPermissionsHelper.IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess);
- default:
- throw new NotSupportedException("Path access is only determined on content or media");
- }
- }
-
///
/// Determines whether this user has access to view sensitive data
///
diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
index d5738b5e3f..6a5acb0dc7 100644
--- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Text.RegularExpressions;
using System.Web;
using System.Xml.Linq;
using System.Xml.XPath;
@@ -12,7 +11,9 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Packaging;
+using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
@@ -26,13 +27,14 @@ namespace Umbraco.Core.Packaging
private readonly ILocalizationService _localizationService;
private readonly IDataTypeService _dataTypeService;
private readonly PropertyEditorCollection _propertyEditors;
+ private readonly IScopeProvider _scopeProvider;
private readonly IEntityService _entityService;
private readonly IContentTypeService _contentTypeService;
private readonly IContentService _contentService;
public PackageDataInstallation(ILogger logger, IFileService fileService, IMacroService macroService, ILocalizationService localizationService,
IDataTypeService dataTypeService, IEntityService entityService, IContentTypeService contentTypeService,
- IContentService contentService, PropertyEditorCollection propertyEditors)
+ IContentService contentService, PropertyEditorCollection propertyEditors, IScopeProvider scopeProvider)
{
_logger = logger;
_fileService = fileService;
@@ -40,12 +42,13 @@ namespace Umbraco.Core.Packaging
_localizationService = localizationService;
_dataTypeService = dataTypeService;
_propertyEditors = propertyEditors;
+ _scopeProvider = scopeProvider;
_entityService = entityService;
_contentTypeService = contentTypeService;
_contentService = contentService;
}
- #region Uninstall
+ #region Install/Uninstall
public UninstallationSummary UninstallPackageData(PackageDefinition package, int userId)
{
@@ -58,93 +61,97 @@ namespace Umbraco.Core.Packaging
var removedDataTypes = new List();
var removedLanguages = new List();
-
- //Uninstall templates
- foreach (var item in package.Templates.ToArray())
+ using (var scope = _scopeProvider.CreateScope())
{
- if (int.TryParse(item, out var nId) == false) continue;
- var found = _fileService.GetTemplate(nId);
- if (found != null)
+ //Uninstall templates
+ foreach (var item in package.Templates.ToArray())
{
- removedTemplates.Add(found);
- _fileService.DeleteTemplate(found.Alias, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var found = _fileService.GetTemplate(nId);
+ if (found != null)
+ {
+ removedTemplates.Add(found);
+ _fileService.DeleteTemplate(found.Alias, userId);
+ }
+ package.Templates.Remove(nId.ToString());
}
- package.Templates.Remove(nId.ToString());
- }
- //Uninstall macros
- foreach (var item in package.Macros.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var macro = _macroService.GetById(nId);
- if (macro != null)
+ //Uninstall macros
+ foreach (var item in package.Macros.ToArray())
{
- removedMacros.Add(macro);
- _macroService.Delete(macro, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var macro = _macroService.GetById(nId);
+ if (macro != null)
+ {
+ removedMacros.Add(macro);
+ _macroService.Delete(macro, userId);
+ }
+ package.Macros.Remove(nId.ToString());
}
- package.Macros.Remove(nId.ToString());
- }
- //Remove Document Types
- var contentTypes = new List();
- var contentTypeService = _contentTypeService;
- foreach (var item in package.DocumentTypes.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var contentType = contentTypeService.Get(nId);
- if (contentType == null) continue;
- contentTypes.Add(contentType);
- package.DocumentTypes.Remove(nId.ToString(CultureInfo.InvariantCulture));
- }
-
- //Order the DocumentTypes before removing them
- if (contentTypes.Any())
- {
- // 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();
- removedContentTypes.AddRange(orderedTypes);
- contentTypeService.Delete(orderedTypes, userId);
- }
-
- //Remove Dictionary items
- foreach (var item in package.DictionaryItems.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var di = _localizationService.GetDictionaryItemById(nId);
- if (di != null)
+ //Remove Document Types
+ var contentTypes = new List();
+ var contentTypeService = _contentTypeService;
+ foreach (var item in package.DocumentTypes.ToArray())
{
- removedDictionaryItems.Add(di);
- _localizationService.Delete(di, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var contentType = contentTypeService.Get(nId);
+ if (contentType == null) continue;
+ contentTypes.Add(contentType);
+ package.DocumentTypes.Remove(nId.ToString(CultureInfo.InvariantCulture));
}
- package.DictionaryItems.Remove(nId.ToString());
- }
- //Remove Data types
- foreach (var item in package.DataTypes.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var dtd = _dataTypeService.GetDataType(nId);
- if (dtd != null)
+ //Order the DocumentTypes before removing them
+ if (contentTypes.Any())
{
- removedDataTypes.Add(dtd);
- _dataTypeService.Delete(dtd, userId);
+ // 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();
+ removedContentTypes.AddRange(orderedTypes);
+ contentTypeService.Delete(orderedTypes, userId);
}
- package.DataTypes.Remove(nId.ToString());
- }
- //Remove Langs
- foreach (var item in package.Languages.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var lang = _localizationService.GetLanguageById(nId);
- if (lang != null)
+ //Remove Dictionary items
+ foreach (var item in package.DictionaryItems.ToArray())
{
- removedLanguages.Add(lang);
- _localizationService.Delete(lang, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var di = _localizationService.GetDictionaryItemById(nId);
+ if (di != null)
+ {
+ removedDictionaryItems.Add(di);
+ _localizationService.Delete(di, userId);
+ }
+ package.DictionaryItems.Remove(nId.ToString());
}
- package.Languages.Remove(nId.ToString());
+
+ //Remove Data types
+ foreach (var item in package.DataTypes.ToArray())
+ {
+ if (int.TryParse(item, out var nId) == false) continue;
+ var dtd = _dataTypeService.GetDataType(nId);
+ if (dtd != null)
+ {
+ removedDataTypes.Add(dtd);
+ _dataTypeService.Delete(dtd, userId);
+ }
+ package.DataTypes.Remove(nId.ToString());
+ }
+
+ //Remove Langs
+ foreach (var item in package.Languages.ToArray())
+ {
+ if (int.TryParse(item, out var nId) == false) continue;
+ var lang = _localizationService.GetLanguageById(nId);
+ if (lang != null)
+ {
+ removedLanguages.Add(lang);
+ _localizationService.Delete(lang, userId);
+ }
+ package.Languages.Remove(nId.ToString());
+ }
+
+ scope.Complete();
}
// create a summary of what was actually removed, for PackagingService.UninstalledPackage
@@ -165,14 +172,40 @@ namespace Umbraco.Core.Packaging
}
+ public InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId)
+ {
+ using (var scope = _scopeProvider.CreateScope())
+ {
+ var installationSummary = new InstallationSummary
+ {
+ DataTypesInstalled = ImportDataTypes(compiledPackage.DataTypes.ToList(), userId),
+ LanguagesInstalled = ImportLanguages(compiledPackage.Languages, userId),
+ DictionaryItemsInstalled = ImportDictionaryItems(compiledPackage.DictionaryItems, userId),
+ MacrosInstalled = ImportMacros(compiledPackage.Macros, userId),
+ TemplatesInstalled = ImportTemplates(compiledPackage.Templates.ToList(), userId),
+ DocumentTypesInstalled = ImportDocumentTypes(compiledPackage.DocumentTypes, userId)
+ };
+
+ //we need a reference to the imported doc types to continue
+ var importedDocTypes = installationSummary.DocumentTypesInstalled.ToDictionary(x => x.Alias, x => x);
+
+ installationSummary.StylesheetsInstalled = ImportStylesheets(compiledPackage.Stylesheets, userId);
+ installationSummary.ContentInstalled = ImportContent(compiledPackage.Documents, importedDocTypes, userId);
+
+ scope.Complete();
+
+ return installationSummary;
+ }
+ }
+
#endregion
#region Content
- public IEnumerable ImportContent(IEnumerable docs, IDictionary importedDocumentTypes, int userId)
+ public IReadOnlyList ImportContent(IEnumerable docs, IDictionary importedDocumentTypes, int userId)
{
- return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId));
+ return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId)).ToList();
}
///
@@ -353,7 +386,7 @@ namespace Umbraco.Core.Packaging
#region DocumentTypes
- public IEnumerable ImportDocumentType(XElement docTypeElement, int userId)
+ public IReadOnlyList ImportDocumentType(XElement docTypeElement, int userId)
{
return ImportDocumentTypes(new[] { docTypeElement }, userId);
}
@@ -364,7 +397,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the User performing the operation. Default is zero (admin).
/// An enumerable list of generated ContentTypes
- public IEnumerable ImportDocumentTypes(IEnumerable docTypeElements, int userId)
+ public IReadOnlyList ImportDocumentTypes(IEnumerable docTypeElements, int userId)
{
return ImportDocumentTypes(docTypeElements.ToList(), true, userId);
}
@@ -376,7 +409,7 @@ namespace Umbraco.Core.Packaging
/// Boolean indicating whether or not to import the
/// Optional id of the User performing the operation. Default is zero (admin).
/// An enumerable list of generated ContentTypes
- public IEnumerable ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId)
+ public IReadOnlyList ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId)
{
var importedContentTypes = new Dictionary();
@@ -575,12 +608,11 @@ namespace Umbraco.Core.Packaging
contentType.Thumbnail = infoElement.Element("Thumbnail").Value;
contentType.Description = infoElement.Element("Description").Value;
- //NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it.
+ //NOTE AllowAtRoot, IsListView, IsElement and Variations are new properties in the package xml so we need to verify it exists before using it.
var allowAtRoot = infoElement.Element("AllowAtRoot");
if (allowAtRoot != null)
contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true");
- //NOTE IsListView is a new property in the package xml so we need to verify it exists before using it.
var isListView = infoElement.Element("IsListView");
if (isListView != null)
contentType.IsContainer = isListView.Value.InvariantEquals("true");
@@ -589,6 +621,10 @@ namespace Umbraco.Core.Packaging
if (isElement != null)
contentType.IsElement = isElement.Value.InvariantEquals("true");
+ var variationsElement = infoElement.Element("Variations");
+ if (variationsElement != null)
+ contentType.Variations = (ContentVariation)Enum.Parse(typeof(ContentVariation), variationsElement.Value);
+
//Name of the master corresponds to the parent and we need to ensure that the Parent Id is set
var masterElement = infoElement.Element("Master");
if (masterElement != null)
@@ -614,7 +650,7 @@ namespace Umbraco.Core.Packaging
var compositionContentType = importedContentTypes.ContainsKey(compositionAlias)
? importedContentTypes[compositionAlias]
: _contentTypeService.Get(compositionAlias);
- var added = contentType.AddContentType(compositionContentType);
+ contentType.AddContentType(compositionContentType);
}
}
}
@@ -748,11 +784,21 @@ namespace Umbraco.Core.Packaging
{
Name = property.Element("Name").Value,
Description = (string)property.Element("Description"),
- Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false,
- MandatoryMessage = property.Element("MandatoryMessage") != null ? (string)property.Element("MandatoryMessage") : string.Empty,
+ Mandatory = property.Element("Mandatory") != null
+ ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true")
+ : false,
+ MandatoryMessage = property.Element("MandatoryMessage") != null
+ ? (string)property.Element("MandatoryMessage")
+ : string.Empty,
+
ValidationRegExp = (string)property.Element("Validation"),
- ValidationRegExpMessage = property.Element("ValidationRegExpMessage") != null ? (string)property.Element("ValidationRegExpMessage") : string.Empty,
- SortOrder = sortOrder
+ ValidationRegExpMessage = property.Element("ValidationRegExpMessage") != null
+ ? (string)property.Element("ValidationRegExpMessage")
+ : string.Empty,
+ SortOrder = sortOrder,
+ Variations = property.Element("Variations") != null
+ ? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value)
+ : ContentVariation.Nothing
};
var tab = (string)property.Element("Tab");
@@ -819,7 +865,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the user
/// An enumerable list of generated DataTypeDefinitions
- public IEnumerable ImportDataTypes(IReadOnlyCollection dataTypeElements, int userId)
+ public IReadOnlyList ImportDataTypes(IReadOnlyCollection dataTypeElements, int userId)
{
var dataTypes = new List();
@@ -948,13 +994,13 @@ namespace Umbraco.Core.Packaging
/// Xml to import
///
/// An enumerable list of dictionary items
- public IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, int userId)
+ public IReadOnlyList ImportDictionaryItems(IEnumerable dictionaryItemElementList, int userId)
{
var languages = _localizationService.GetAllLanguages().ToList();
return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId);
}
- private IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, List languages, Guid? parentId, int userId)
+ private IReadOnlyList ImportDictionaryItems(IEnumerable dictionaryItemElementList, List languages, Guid? parentId, int userId)
{
var items = new List();
foreach (var dictionaryItemElement in dictionaryItemElementList)
@@ -1031,7 +1077,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the User performing the operation
/// An enumerable list of generated languages
- public IEnumerable ImportLanguages(IEnumerable languageElements, int userId)
+ public IReadOnlyList ImportLanguages(IEnumerable languageElements, int userId)
{
var list = new List();
foreach (var languageElement in languageElements)
@@ -1060,7 +1106,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the User performing the operation
///
- public IEnumerable ImportMacros(IEnumerable macroElements, int userId)
+ public IReadOnlyList ImportMacros(IEnumerable macroElements, int userId)
{
var macros = macroElements.Select(ParseMacroElement).ToList();
@@ -1150,7 +1196,7 @@ namespace Umbraco.Core.Packaging
#region Stylesheets
- public IEnumerable ImportStylesheets(IEnumerable stylesheetElements, int userId)
+ public IReadOnlyList ImportStylesheets(IEnumerable stylesheetElements, int userId)
{
var result = new List();
@@ -1218,7 +1264,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional user id
/// An enumerable list of generated Templates
- public IEnumerable ImportTemplates(IReadOnlyCollection templateElements, int userId)
+ public IReadOnlyList ImportTemplates(IReadOnlyCollection templateElements, int userId)
{
var templates = new List();
diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs
index d791295b38..a42ee1aeb2 100644
--- a/src/Umbraco.Core/Packaging/PackageInstallation.cs
+++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs
@@ -90,21 +90,8 @@ namespace Umbraco.Core.Packaging
public InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId)
{
- var installationSummary = new InstallationSummary
- {
- DataTypesInstalled = _packageDataInstallation.ImportDataTypes(compiledPackage.DataTypes.ToList(), userId),
- LanguagesInstalled = _packageDataInstallation.ImportLanguages(compiledPackage.Languages, userId),
- DictionaryItemsInstalled = _packageDataInstallation.ImportDictionaryItems(compiledPackage.DictionaryItems, userId),
- MacrosInstalled = _packageDataInstallation.ImportMacros(compiledPackage.Macros, userId),
- TemplatesInstalled = _packageDataInstallation.ImportTemplates(compiledPackage.Templates.ToList(), userId),
- DocumentTypesInstalled = _packageDataInstallation.ImportDocumentTypes(compiledPackage.DocumentTypes, userId)
- };
+ var installationSummary = _packageDataInstallation.InstallPackageData(compiledPackage, userId);
- //we need a reference to the imported doc types to continue
- var importedDocTypes = installationSummary.DocumentTypesInstalled.ToDictionary(x => x.Alias, x => x);
-
- installationSummary.StylesheetsInstalled = _packageDataInstallation.ImportStylesheets(compiledPackage.Stylesheets, userId);
- installationSummary.ContentInstalled = _packageDataInstallation.ImportContent(compiledPackage.Documents, importedDocTypes, userId);
installationSummary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(compiledPackage.Actions), compiledPackage.Name);
installationSummary.MetaData = compiledPackage;
installationSummary.FilesInstalled = packageDefinition.Files;
diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
index f1473b5888..33dabe1b24 100644
--- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId, ILanguageRepository languageRepository)
{
var properties = new List();
- var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable) x);
+ var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable)x);
foreach (var propertyType in propertyTypes)
{
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
/// The properties to map
///
/// out parameter indicating that one or more properties have been edited
- /// out parameter containing a collection of edited cultures when the contentVariation varies by culture
+ ///
+ /// Out parameter containing a collection of edited cultures when the contentVariation varies by culture.
+ /// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
+ ///
///
public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties,
- ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures)
+ ILanguageRepository languageRepository, out bool edited,
+ out HashSet editedCultures)
{
var propertyDataDtos = new List();
edited = false;
@@ -130,6 +134,9 @@ namespace Umbraco.Core.Persistence.Factories
// publishing = deal with edit and published values
foreach (var propertyValue in property.Values)
{
+ var isInvariantValue = propertyValue.Culture == null;
+ var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
+
// deal with published value
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
@@ -138,26 +145,36 @@ namespace Umbraco.Core.Persistence.Factories
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
+ // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
+ // administrator has previously changed the property type to be variant vs invariant.
+ // We need to check for this scenario here because otherwise the editedCultures and edited flags
+ // will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to
+ // only process edited cultures based on the current value type and how the property varies.
+ // The above logic will still persist the currently saved property value for each culture in case the admin
+ // decides to swap the property's variance again, in which case the edited flag will be recalculated.
+
+ if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue)
+ continue;
+
// use explicit equals here, else object comparison fails at comparing eg strings
var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue);
+
edited |= !sameValues;
- if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported
- && propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral
- && !sameValues) // and edited and published are different
+ if (entityVariesByCulture && !sameValues)
{
- editedCultures.Add(propertyValue.Culture); // report culture as edited
- }
+ if (isCultureValue)
+ {
+ editedCultures.Add(propertyValue.Culture); // report culture as edited
+ }
+ else if (isInvariantValue)
+ {
+ // flag culture as edited if it contains an edited invariant property
+ if (defaultCulture == null)
+ defaultCulture = languageRepository.GetDefaultIsoCode();
- // flag culture as edited if it contains an edited invariant property
- if (propertyValue.Culture == null //invariant property
- && !sameValues // and edited and published are different
- && entityVariesByCulture) //only when the entity is variant
- {
- if (defaultCulture == null)
- defaultCulture = languageRepository.GetDefaultIsoCode();
-
- editedCultures.Add(defaultCulture);
+ editedCultures.Add(defaultCulture);
+ }
}
}
}
@@ -167,7 +184,7 @@ namespace Umbraco.Core.Persistence.Factories
{
// not publishing = only deal with edit values
if (propertyValue.EditedValue != null)
- propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
+ propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
}
edited = true;
}
diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs
index 0846d6d1f6..dc1629e8f7 100644
--- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs
@@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Factories
propertyType.Alias = typeDto.Alias;
propertyType.DataTypeId = typeDto.DataTypeId;
+ propertyType.DataTypeKey = typeDto.DataTypeDto.NodeDto.UniqueId;
propertyType.Description = typeDto.Description;
propertyType.Id = typeDto.Id;
propertyType.Key = typeDto.UniqueId;
diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs
index 853cd9f99e..48e7afdc7e 100644
--- a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs
+++ b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs
@@ -18,7 +18,8 @@ namespace Umbraco.Core.Persistence.Mappers
DefineMap(nameof(AuditItem.Id), nameof(LogDto.NodeId));
DefineMap(nameof(AuditItem.CreateDate), nameof(LogDto.Datestamp));
DefineMap(nameof(AuditItem.UserId), nameof(LogDto.UserId));
- DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header));
+ // we cannot map that one - because AuditType is an enum but Header is a string
+ //DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header));
DefineMap(nameof(AuditItem.Comment), nameof(LogDto.Comment));
}
}
diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs
index 03d82a345f..c87937e41e 100644
--- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs
+++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs
@@ -85,6 +85,17 @@ namespace Umbraco.Core.Persistence.Querying
// I'm just unsure right now due to time constraints how to make it correct. It won't matter right now and has been working already with this bug but I've
// only just discovered what it is actually doing.
+ // TODO
+ // in most cases we want to convert the value to a plain object,
+ // but for in some rare cases, we may want to do it differently,
+ // for instance a Models.AuditType (an enum) may in some cases
+ // need to be converted to its string value.
+ // but - we cannot have specific code here, really - and how would
+ // we configure this? is it even possible?
+ /*
+ var toString = typeof(object).GetMethod("ToString");
+ var member = Expression.Call(m, toString);
+ */
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda>(member);
var getter = lambda.Compile();
diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs
index 7c8a82bb85..b2dd6a3297 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs
@@ -33,5 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories
Direction orderDirection,
AuditType[] auditTypeFilter,
IQuery customFilter);
+
+ IEnumerable Get(AuditType type, IQuery query);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs
index 1486935e2a..c3d34cc3e9 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs
@@ -100,7 +100,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
protected override void PersistNewItem(IAuditEntry entity)
{
- ((EntityBase) entity).AddingEntity();
+ entity.AddingEntity();
var dto = AuditEntryFactory.BuildDto(entity);
Database.Insert(dto);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs
index cda89fd89a..0788594e3a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs
@@ -74,6 +74,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList();
}
+ public IEnumerable Get(AuditType type, IQuery query)
+ {
+ var sqlClause = GetBaseQuery(false)
+ .Where(x => x.Header == type.ToString());
+ var translator = new SqlTranslator(sqlClause, query);
+ var sql = translator.Translate();
+
+ var dtos = Database.Fetch(sql);
+
+ return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList();
+ }
+
protected override Sql GetBaseQuery(bool isCount)
{
var sql = SqlContext.Sql();
@@ -162,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
totalRecords = page.TotalItems;
var items = page.Items.Select(
- dto => new AuditItem(dto.Id, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
+ dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
// map the DateStamp
for (var i = 0; i < items.Count; i++)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs
index 8df9bf686d..57d5dfa864 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs
@@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
protected override void PersistNewItem(IConsent entity)
{
- ((EntityBase) entity).AddingEntity();
+ entity.AddingEntity();
var dto = ConsentFactory.BuildDto(entity);
Database.Insert(dto);
@@ -80,7 +80,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
protected override void PersistUpdatedItem(IConsent entity)
{
- ((EntityBase) entity).UpdatingEntity();
+ entity.UpdatingEntity();
var dto = ConsentFactory.BuildDto(entity);
Database.Update(dto);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index aeb4c3774f..7ab73f3f2d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -512,31 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var a in allPropertyDataDtos)
a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId];
- // prefetch configuration for tag properties
- var tagEditors = new Dictionary();
- foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values)
- {
- var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias;
- var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute();
- if (editorAttribute == null) continue;
- var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration;
- var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource)
- ? new TagConfiguration()
- : JsonConvert.DeserializeObject(tagConfigurationSource);
- if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter;
- tagEditors[editorAlias] = tagConfiguration;
- }
-
// now we have
// - the definitions
// - all property data dtos
- // - tag editors
+ // - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
// and we need to build the proper property collections
- return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
+ return GetPropertyCollections(temps, allPropertyDataDtos);
}
- private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos, Dictionary tagConfigurations)
+ private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos)
where T : class, IContentBase
{
var result = new Dictionary();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
index 8cbcf4a523..7781e2e38a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Scoping;
+using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -89,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
- else throw new Exception("panic");
+ else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
contentTypes.Add(contentType.Id, contentType);
// map allowed content types
@@ -188,10 +190,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var groupDtos = Database.Fetch(sql1);
var sql2 = Sql()
- .Select(r => r.Select(x => x.DataTypeDto))
+ .Select(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto)))
.AndSelect()
.From()
.InnerJoin().On((pt, dt) => pt.DataTypeId == dt.NodeId)
+ .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId)
.InnerJoin().On((pt, ct) => pt.ContentTypeId == ct.NodeId)
.LeftJoin().On((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id)
.LeftJoin().On((pt, mpt) => pt.Id == mpt.PropertyTypeId)
@@ -290,6 +293,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
Description = dto.Description,
DataTypeId = dto.DataTypeId,
+ DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId,
Id = dto.Id,
Key = dto.UniqueId,
Mandatory = dto.Mandatory,
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
index 98ddcdcb17..359b967dab 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -17,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository
{
- public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
- : base(scopeAccessor, cache, logger, commonRepository)
+ public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
+ : base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
@@ -55,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
- if (ids.Any()) throw new Exception("panic");
+ if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType();
}
@@ -230,7 +232,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
throw ex;
}
- ((ContentType)entity).AddingEntity();
+ entity.AddingEntity();
PersistNewBaseContentType(entity);
PersistTemplates(entity, false);
@@ -270,7 +272,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ValidateAlias(entity);
//Updates Modified date
- ((ContentType)entity).UpdatingEntity();
+ entity.UpdatingEntity();
//Look up parent to get and set the correct Path if ParentId has changed
if (entity.IsPropertyDirty("ParentId"))
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 591fa2b660..f2efb03ba4 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
+using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -26,14 +27,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository
where TEntity : class, IContentTypeComposition
{
- protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
+ protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger)
{
CommonRepository = commonRepository;
+ LanguageRepository = languageRepository;
}
protected IContentTypeCommonRepository CommonRepository { get; }
-
+ protected ILanguageRepository LanguageRepository { get; }
protected abstract bool SupportsPublishing { get; }
public IEnumerable> Move(TEntity moving, EntityContainer container)
@@ -98,6 +100,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected void PersistNewBaseContentType(IContentTypeComposition entity)
{
+ ValidateVariations(entity);
+
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
//Cannot add a duplicate content type
@@ -163,11 +167,11 @@ AND umbracoNode.nodeObjectType = @objectType",
foreach (var allowedContentType in entity.AllowedContentTypes)
{
Database.Insert(new ContentTypeAllowedContentTypeDto
- {
- Id = entity.Id,
- AllowedId = allowedContentType.Id.Value,
- SortOrder = allowedContentType.SortOrder
- });
+ {
+ Id = entity.Id,
+ AllowedId = allowedContentType.Id.Value,
+ SortOrder = allowedContentType.SortOrder
+ });
}
@@ -214,6 +218,8 @@ AND umbracoNode.nodeObjectType = @objectType",
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
{
+ ValidateVariations(entity);
+
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
// ensure the alias is not used already
@@ -370,7 +376,7 @@ AND umbracoNode.id <> @id",
foreach (var propertyGroup in entity.PropertyGroups)
{
// insert or update group
- var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id);
+ var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id);
var groupId = propertyGroup.HasIdentity
? Database.Update(groupDto)
: Convert.ToInt32(Database.Insert(groupDto));
@@ -388,7 +394,7 @@ AND umbracoNode.id <> @id",
//check if the content type variation has been changed
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
- var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
+ var oldContentTypeVariation = (ContentVariation)dtoPk.Variations;
var newContentTypeVariation = entity.Variations;
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
if (contentTypeVariationChanging)
@@ -449,7 +455,7 @@ AND umbracoNode.id <> @id",
// via composition, with their original variations (ie not filtered by this
// content type variations - we need this true value to make decisions.
- foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes)
+ foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
{
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
throw new NotSupportedException(); // TODO: support this
@@ -518,6 +524,25 @@ AND umbracoNode.id <> @id",
CommonRepository.ClearCache(); // always
}
+ ///
+ /// Ensures that no property types are flagged for a variance that is not supported by the content type itself
+ ///
+ ///
+ private void ValidateVariations(IContentTypeComposition entity)
+ {
+ //if the entity does not vary at all, then the property cannot have a variance value greater than it
+ if (entity.Variations == ContentVariation.Nothing)
+ {
+ foreach (var prop in entity.PropertyTypes)
+ {
+ if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations)
+ throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
+ }
+
+ }
+
+ }
+
private IEnumerable GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable all)
{
var impact = new List();
@@ -525,12 +550,12 @@ AND umbracoNode.id <> @id",
var tree = new Dictionary>();
foreach (var x in all)
- foreach (var y in x.ContentTypeComposition)
- {
- if (!tree.TryGetValue(y.Id, out var list))
- list = tree[y.Id] = new List();
- list.Add(x);
- }
+ foreach (var y in x.ContentTypeComposition)
+ {
+ if (!tree.TryGetValue(y.Id, out var list))
+ list = tree[y.Id] = new List();
+ list.Add(x);
+ }
var nset = new List();
do
@@ -572,7 +597,7 @@ AND umbracoNode.id <> @id",
// new property type, ignore
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
continue;
- var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
+ var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly
// only those property types that *actually* changed
var newVariation = propertyType.Variations;
@@ -636,7 +661,7 @@ AND umbracoNode.id <> @id",
var impactedL = impacted.Select(x => x.Id).ToList();
//Group by the "To" variation so we can bulk update in the correct batches
- foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
+ foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
{
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
var toVariation = grouping.Key;
@@ -646,10 +671,12 @@ AND umbracoNode.id <> @id",
case ContentVariation.Culture:
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
+ RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.Nothing:
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
+ RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
@@ -963,6 +990,205 @@ AND umbracoNode.id <> @id",
Database.Execute(sqlDelete);
}
+
+ }
+
+ ///
+ /// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
+ ///
+ ///
+ ///
+ ///
+ /// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false
+ /// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each
+ /// property, culture and current/published version.
+ ///
+ private void RenormalizeDocumentEditedFlags(IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null)
+ {
+ var defaultLang = LanguageRepository.GetDefaultId();
+
+ //This will build up a query to get the property values of both the current and the published version so that we can check
+ //based on the current variance of each item to see if it's 'edited' value should be true/false.
+
+ var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
+ if (whereInArgsCount > 2000)
+ throw new NotSupportedException("Too many property/content types.");
+
+ var propertySql = Sql()
+ .Select()
+ .AndSelect(x => x.NodeId, x => x.Current)
+ .AndSelect(x => x.Published)
+ .AndSelect(x => x.Variations)
+ .From()
+ .InnerJoin().On((left, right) => left.Id == right.VersionId)
+ .InnerJoin().On((left, right) => left.Id == right.PropertyTypeId);
+
+ if (contentTypeIds != null)
+ {
+ propertySql.InnerJoin().On((c, cversion) => c.NodeId == cversion.NodeId);
+ }
+
+ propertySql.LeftJoin().On((docversion, cversion) => cversion.Id == docversion.Id)
+ .Where((docversion, cversion) => cversion.Current || docversion.Published)
+ .WhereIn(x => x.PropertyTypeId, propertyTypeIds);
+
+ if (contentTypeIds != null)
+ {
+ propertySql.WhereIn(x => x.ContentTypeId, contentTypeIds);
+ }
+
+ propertySql
+ .OrderBy(x => x.NodeId)
+ .OrderBy(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId);
+
+ //keep track of this node/lang to mark or unmark a culture as edited
+ var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
+ //keep track of which node to mark or unmark as edited
+ var editedDocument = new Dictionary();
+ var nodeId = -1;
+ var propertyTypeId = -1;
+
+ PropertyValueVersionDto pubRow = null;
+
+ //This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data.
+ //Published data will always come before Current data based on the version id sort.
+ //There will only be one published row (max) and one current row per property.
+ foreach (var row in Database.Query(propertySql))
+ {
+ //make sure to reset on each node/property change
+ if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId)
+ {
+ nodeId = row.NodeId;
+ propertyTypeId = row.PropertyTypeId;
+ pubRow = null;
+ }
+
+ if (row.Published)
+ pubRow = row;
+
+ if (row.Current)
+ {
+ var propVariations = (ContentVariation)row.Variations;
+
+ //if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited
+ if (!propVariations.VariesByCulture() && row.LanguageId.HasValue
+ || propVariations.VariesByCulture() && !row.LanguageId.HasValue)
+ {
+ //Flag this as not edited for this node/lang if the key doesn't exist
+ if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _))
+ editedLanguageVersions.Add((row.NodeId, row.LanguageId), false);
+
+ //mark as false if the item doesn't exist, else coerce to true
+ editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false;
+ }
+ else if (pubRow == null)
+ {
+ //this would mean that that this property is 'edited' since there is no published version
+ editedLanguageVersions[(row.NodeId, row.LanguageId)] = true;
+ editedDocument[row.NodeId] = true;
+ }
+ //compare the property values, if they differ from versions then flag the current version as edited
+ else if (IsPropertyValueChanged(pubRow, row))
+ {
+ //Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang
+ editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true;
+ editedDocument[row.NodeId] = true;
+ }
+
+ //reset
+ pubRow = null;
+ }
+ }
+
+ //lookup all matching rows in umbracoDocumentCultureVariation
+ var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
+ .SelectMany(_ => Database.Fetch(
+ Sql().Select().From()
+ .WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
+ .WhereIn(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
+ //convert to dictionary with the same key type
+ .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
+
+ var toUpdate = new List();
+ foreach (var ev in editedLanguageVersions)
+ {
+ if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations))
+ {
+ //check if it needs updating
+ if (docVariations.Edited != ev.Value)
+ {
+ docVariations.Edited = ev.Value;
+ toUpdate.Add(docVariations);
+ }
+ }
+ else if (ev.Key.langId.HasValue)
+ {
+ //This should never happen! If a property culture is flagged as edited then the culture must exist at the document level
+ throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}");
+ }
+ }
+
+ //Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
+ foreach (var editValue in toUpdate.GroupBy(x => x.Edited))
+ {
+ Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key))
+ .WhereIn(x => x.Id, editValue.Select(x => x.Id)));
+ }
+
+ //Now bulk update the umbracoDocument table
+ foreach (var editValue in editedDocument.GroupBy(x => x.Value))
+ {
+ Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key))
+ .WhereIn(x => x.NodeId, editValue.Select(x => x.Key)));
+ }
+ }
+
+ private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row)
+ {
+ return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue
+ || !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue
+ || pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue
+ || pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue
+ || pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue;
+ }
+
+ private class NameCompareDto
+ {
+ public int NodeId { get; set; }
+ public int CurrentVersion { get; set; }
+ public int LanguageId { get; set; }
+ public string CurrentName { get; set; }
+ public string PublishedName { get; set; }
+ public int? PublishedVersion { get; set; }
+ public int Id { get; set; } // the Id of the DocumentCultureVariationDto
+ public bool Edited { get; set; }
+ }
+
+ private class PropertyValueVersionDto
+ {
+ public int VersionId { get; set; }
+ public int PropertyTypeId { get; set; }
+ public int? LanguageId { get; set; }
+ public string Segment { get; set; }
+ public int? IntValue { get; set; }
+
+ private decimal? _decimalValue;
+ [Column("decimalValue")]
+ public decimal? DecimalValue
+ {
+ get => _decimalValue;
+ set => _decimalValue = value?.Normalize();
+ }
+
+ public DateTime? DateValue { get; set; }
+ public string VarcharValue { get; set; }
+ public string TextValue { get; set; }
+
+ public int NodeId { get; set; }
+ public bool Current { get; set; }
+ public bool Published { get; set; }
+
+ public byte Variations { get; set; }
}
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
@@ -1013,8 +1239,9 @@ AND umbracoNode.id <> @id",
if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false)
{
var sql = Sql()
- .SelectAll()
+ .Select(dt => dt.Select(x => x.NodeDto))
.From()
+ .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId)
.Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias })
.OrderBy(typeDto => typeDto.NodeId);
var datatype = Database.FirstOrDefault(sql);
@@ -1022,6 +1249,7 @@ AND umbracoNode.id <> @id",
if (datatype != null)
{
propertyType.DataTypeId = datatype.NodeId;
+ propertyType.DataTypeKey = datatype.NodeDto.UniqueId;
}
else
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs
index 02ba4d1c13..dac8fda5ec 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
@@ -106,7 +107,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IDataType entity)
{
- ((DataType)entity).AddingEntity();
+ entity.AddingEntity();
//ensure a datatype has a unique name before creating it
entity.Name = EnsureUniqueNodeName(entity.Name);
@@ -174,7 +175,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
//Updates Modified date
- ((DataType)entity).UpdatingEntity();
+ entity.UpdatingEntity();
//Look up parent to get and set the correct Path if ParentId has changed
if (entity.IsPropertyDirty("ParentId"))
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
index be1e28fcc1..0b58663952 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs
@@ -148,7 +148,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IDictionaryItem entity)
{
- ((EntityBase)entity).UpdatingEntity();
+ entity.UpdatingEntity();
foreach (var translation in entity.Translations)
translation.Value = translation.Value.ToValidXmlString();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index 6c08e05995..344557d815 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -6,6 +6,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
@@ -205,7 +206,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
"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 " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.AccessRule + " WHERE accessId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id OR loginNodeId = @id OR noAccessNodeId = @id)",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE loginNodeId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE noAccessNodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id"
};
return list;
@@ -260,21 +264,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IContent entity)
{
- // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4234 - sort out IContent vs Content
- // however, it's not just so we have access to AddingEntity
- // there are tons of things at the end of the methods, that can only work with a true Content
- // and basically, the repository requires a Content, not an IContent
- var content = (Content)entity;
+ entity.AddingEntity();
- content.AddingEntity();
- var publishing = content.PublishedState == PublishedState.Publishing;
+ var publishing = entity.PublishedState == PublishedState.Publishing;
// ensure that the default template is assigned
if (entity.TemplateId.HasValue == false)
entity.TemplateId = entity.ContentType.DefaultTemplate?.Id;
// sanitize names
- SanitizeNames(content, publishing);
+ SanitizeNames(entity, publishing);
// ensure that strings don't contain characters that are invalid in xml
// TODO: do we really want to keep doing this here?
@@ -324,11 +323,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
contentVersionDto.NodeId = nodeDto.NodeId;
contentVersionDto.Current = !publishing;
Database.Insert(contentVersionDto);
- content.VersionId = contentVersionDto.Id;
+ entity.VersionId = contentVersionDto.Id;
// persist the document version dto
var documentVersionDto = dto.DocumentVersionDto;
- documentVersionDto.Id = content.VersionId;
+ documentVersionDto.Id = entity.VersionId;
if (publishing)
documentVersionDto.Published = true;
Database.Insert(documentVersionDto);
@@ -336,62 +335,62 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// and again in case we're publishing immediately
if (publishing)
{
- content.PublishedVersionId = content.VersionId;
+ entity.PublishedVersionId = entity.VersionId;
contentVersionDto.Id = 0;
contentVersionDto.Current = true;
- contentVersionDto.Text = content.Name;
+ contentVersionDto.Text = entity.Name;
Database.Insert(contentVersionDto);
- content.VersionId = contentVersionDto.Id;
+ entity.VersionId = contentVersionDto.Id;
- documentVersionDto.Id = content.VersionId;
+ documentVersionDto.Id = entity.VersionId;
documentVersionDto.Published = false;
Database.Insert(documentVersionDto);
}
// persist the property data
- var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, content.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures);
+ var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, entity.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures);
foreach (var propertyDataDto in propertyDataDtos)
Database.Insert(propertyDataDto);
// if !publishing, we may have a new name != current publish name,
// also impacts 'edited'
- if (!publishing && content.PublishName != content.Name)
+ if (!publishing && entity.PublishName != entity.Name)
edited = true;
// persist the document dto
// at that point, when publishing, the entity still has its old Published value
// so we need to explicitly update the dto to persist the correct value
- if (content.PublishedState == PublishedState.Publishing)
+ if (entity.PublishedState == PublishedState.Publishing)
dto.Published = true;
dto.NodeId = nodeDto.NodeId;
- content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
+ entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
Database.Insert(dto);
//insert the schedule
- PersistContentSchedule(content, false);
+ PersistContentSchedule(entity, false);
// persist the variations
- if (content.ContentType.VariesByCulture())
+ if (entity.ContentType.VariesByCulture())
{
// bump dates to align cultures to version
if (publishing)
- content.AdjustDates(contentVersionDto.VersionDate);
+ entity.AdjustDates(contentVersionDto.VersionDate);
// names also impact 'edited'
// ReSharper disable once UseDeconstruction
- foreach (var cultureInfo in content.CultureInfos)
- if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture))
+ foreach (var cultureInfo in entity.CultureInfos)
+ if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture))
(editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture);
// insert content variations
- Database.BulkInsertRecords(GetContentVariationDtos(content, publishing));
+ Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
- Database.BulkInsertRecords(GetDocumentVariationDtos(content, publishing, editedCultures));
+ Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
- content.SetCultureEdited(editedCultures);
+ entity.SetCultureEdited(editedCultures);
// trigger here, before we reset Published etc
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
@@ -399,23 +398,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// flip the entity's published property
// this also flips its published state
// note: what depends on variations (eg PublishNames) is managed directly by the content
- if (content.PublishedState == PublishedState.Publishing)
+ if (entity.PublishedState == PublishedState.Publishing)
{
- content.Published = true;
- content.PublishTemplateId = content.TemplateId;
- content.PublisherId = content.WriterId;
- content.PublishName = content.Name;
- content.PublishDate = content.UpdateDate;
+ entity.Published = true;
+ entity.PublishTemplateId = entity.TemplateId;
+ entity.PublisherId = entity.WriterId;
+ entity.PublishName = entity.Name;
+ entity.PublishDate = entity.UpdateDate;
SetEntityTags(entity, _tagRepository);
}
- else if (content.PublishedState == PublishedState.Unpublishing)
+ else if (entity.PublishedState == PublishedState.Unpublishing)
{
- content.Published = false;
- content.PublishTemplateId = null;
- content.PublisherId = null;
- content.PublishName = null;
- content.PublishDate = null;
+ entity.Published = false;
+ entity.PublishTemplateId = null;
+ entity.PublisherId = null;
+ entity.PublishName = null;
+ entity.PublishDate = null;
ClearEntityTags(entity, _tagRepository);
}
@@ -437,34 +436,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IContent entity)
{
- // however, it's not just so we have access to AddingEntity
- // there are tons of things at the end of the methods, that can only work with a true Content
- // and basically, the repository requires a Content, not an IContent
- var content = (Content)entity;
+ var entityBase = entity as EntityBase;
+ var isEntityDirty = entityBase != null && entityBase.IsDirty();
// check if we need to make any database changes at all
- if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished)
- && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty())
+ if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished)
+ && !isEntityDirty && !entity.IsAnyUserPropertyDirty())
return; // no change to save, do nothing, don't even update dates
// whatever we do, we must check that we are saving the current version
- var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == content.VersionId)).FirstOrDefault();
+ var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == entity.VersionId)).FirstOrDefault();
if (version == null || !version.Current)
throw new InvalidOperationException("Cannot save a non-current version.");
// update
- content.UpdatingEntity();
- var publishing = content.PublishedState == PublishedState.Publishing;
+ entity.UpdatingEntity();
+
+ var publishing = entity.PublishedState == PublishedState.Publishing;
// check if we need to create a new version
- if (publishing && content.PublishedVersionId > 0)
+ if (publishing && entity.PublishedVersionId > 0)
{
// published version is not published anymore
- Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == content.PublishedVersionId));
+ Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == entity.PublishedVersionId));
}
// sanitize names
- SanitizeNames(content, publishing);
+ SanitizeNames(entity, publishing);
// ensure that strings don't contain characters that are invalid in xml
// TODO: do we really want to keep doing this here?
@@ -504,45 +502,45 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// and, if publishing, insert new content & document version dtos
if (publishing)
{
- content.PublishedVersionId = content.VersionId;
+ entity.PublishedVersionId = entity.VersionId;
contentVersionDto.Id = 0; // want a new id
contentVersionDto.Current = true; // current version
- contentVersionDto.Text = content.Name;
+ contentVersionDto.Text = entity.Name;
Database.Insert(contentVersionDto);
- content.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
+ entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
documentVersionDto.Published = false; // non-published version
- Database.Insert(documentVersionDto);
+ Database.Insert(documentVersionDto);
}
// replace the property data (rather than updating)
// only need to delete for the version that existed, the new version (if any) has no property data yet
- var versionToDelete = publishing ? content.PublishedVersionId : content.VersionId;
+ var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId;
var deletePropertyDataSql = Sql().Delete().Where(x => x.VersionId == versionToDelete);
Database.Execute(deletePropertyDataSql);
// insert property data
- var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, publishing ? content.PublishedVersionId : 0,
+ var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0,
entity.Properties, LanguageRepository, out var edited, out var editedCultures);
foreach (var propertyDataDto in propertyDataDtos)
Database.Insert(propertyDataDto);
// if !publishing, we may have a new name != current publish name,
// also impacts 'edited'
- if (!publishing && content.PublishName != content.Name)
+ if (!publishing && entity.PublishName != entity.Name)
edited = true;
- if (content.ContentType.VariesByCulture())
+ if (entity.ContentType.VariesByCulture())
{
// bump dates to align cultures to version
if (publishing)
- content.AdjustDates(contentVersionDto.VersionDate);
+ entity.AdjustDates(contentVersionDto.VersionDate);
// names also impact 'edited'
// ReSharper disable once UseDeconstruction
- foreach (var cultureInfo in content.CultureInfos)
- if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture))
+ foreach (var cultureInfo in entity.CultureInfos)
+ if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture))
{
edited = true;
(editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture);
@@ -560,7 +558,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Execute(deleteContentVariations);
// replace the document version variations (rather than updating)
- var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == content.Id);
+ var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == entity.Id);
Database.Execute(deleteDocumentVariations);
// TODO: NPoco InsertBulk issue?
@@ -570,32 +568,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// (same in PersistNewItem above)
// insert content variations
- Database.BulkInsertRecords(GetContentVariationDtos(content, publishing));
+ Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
- Database.BulkInsertRecords(GetDocumentVariationDtos(content, publishing, editedCultures));
+ Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
- content.SetCultureEdited(editedCultures);
+ entity.SetCultureEdited(editedCultures);
// update the document dto
// at that point, when un/publishing, the entity still has its old Published value
// so we need to explicitly update the dto to persist the correct value
- if (content.PublishedState == PublishedState.Publishing)
+ if (entity.PublishedState == PublishedState.Publishing)
dto.Published = true;
- else if (content.PublishedState == PublishedState.Unpublishing)
+ else if (entity.PublishedState == PublishedState.Unpublishing)
dto.Published = false;
- content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
+ entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
Database.Update(dto);
//update the schedule
- if (content.IsPropertyDirty("ContentSchedule"))
- PersistContentSchedule(content, true);
+ if (entity.IsPropertyDirty("ContentSchedule"))
+ PersistContentSchedule(entity, true);
// if entity is publishing, update tags, else leave tags there
// means that implicitly unpublished, or trashed, entities *still* have tags in db
- if (content.PublishedState == PublishedState.Publishing)
+ if (entity.PublishedState == PublishedState.Publishing)
SetEntityTags(entity, _tagRepository);
// trigger here, before we reset Published etc
@@ -603,23 +601,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// flip the entity's published property
// this also flips its published state
- if (content.PublishedState == PublishedState.Publishing)
+ if (entity.PublishedState == PublishedState.Publishing)
{
- content.Published = true;
- content.PublishTemplateId = content.TemplateId;
- content.PublisherId = content.WriterId;
- content.PublishName = content.Name;
- content.PublishDate = content.UpdateDate;
+ entity.Published = true;
+ entity.PublishTemplateId = entity.TemplateId;
+ entity.PublisherId = entity.WriterId;
+ entity.PublishName = entity.Name;
+ entity.PublishDate = entity.UpdateDate;
SetEntityTags(entity, _tagRepository);
}
- else if (content.PublishedState == PublishedState.Unpublishing)
+ else if (entity.PublishedState == PublishedState.Unpublishing)
{
- content.Published = false;
- content.PublishTemplateId = null;
- content.PublisherId = null;
- content.PublishName = null;
- content.PublishDate = null;
+ entity.Published = false;
+ entity.PublishTemplateId = null;
+ entity.PublisherId = null;
+ entity.PublishName = null;
+ entity.PublishDate = null;
ClearEntityTags(entity, _tagRepository);
}
@@ -1299,25 +1297,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
};
}
- private IEnumerable GetDocumentVariationDtos(IContent content, bool publishing, HashSet editedCultures)
+ private IEnumerable GetDocumentVariationDtos(IContent content, HashSet editedCultures)
{
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
foreach (var culture in allCultures)
- yield return new DocumentCultureVariationDto
+ {
+ var dto = new DocumentCultureVariationDto
{
NodeId = content.Id,
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = content.GetCultureName(culture) ?? content.GetPublishName(culture),
-
- // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
-
Available = content.IsCultureAvailable(culture),
Published = content.IsCulturePublished(culture),
+ // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Edited = content.IsCultureAvailable(culture) &&
(!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
};
+
+ yield return dto;
+ }
+
}
private class ContentVariation
@@ -1335,7 +1336,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Utilities
- private void SanitizeNames(Content content, bool publishing)
+ private void SanitizeNames(IContent content, bool publishing)
{
// a content item *must* have an invariant name, and invariant published name
// else we just cannot write the invariant rows (node, content version...) to the database
@@ -1400,7 +1401,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
x.NodeId != SqlTemplate.Arg("id"))
.OrderBy(x => x.LanguageId));
- private void EnsureVariantNamesAreUnique(Content content, bool publishing)
+ private void EnsureVariantNamesAreUnique(IContent content, bool publishing)
{
if (!EnsureUniqueNaming || !content.ContentType.VariesByCulture() || content.CultureInfos.Count == 0) return;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs
index 69523a860a..9aa28fb18a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs
@@ -6,6 +6,7 @@ using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
@@ -101,7 +102,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value);
}
- ((UmbracoDomain)entity).AddingEntity();
+ entity.AddingEntity();
var factory = new DomainModelFactory();
var dto = factory.BuildDto(entity);
@@ -120,7 +121,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IDomain entity)
{
- ((UmbracoDomain)entity).UpdatingEntity();
+ entity.UpdatingEntity();
var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomain WHERE domainName = @domainName AND umbracoDomain.id <> @id",
new { domainName = entity.DomainName, id = entity.Id });
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
index 4a32e373c1..b6d39fe54f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
@@ -404,7 +404,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isMedia)
{
sql
- .InnerJoin().On((left, right) => left.Id == right.Id);
+ .LeftJoin().On((left, right) => left.Id == right.Id);
}
//Any LeftJoin statements need to come last
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs
index 0fa48e5521..f708590ea8 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs
@@ -139,7 +139,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IIdentityUserLogin entity)
{
- ((EntityBase)entity).AddingEntity();
+ entity.AddingEntity();
var dto = ExternalLoginFactory.BuildDto(entity);
@@ -151,7 +151,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IIdentityUserLogin entity)
{
- ((EntityBase)entity).UpdatingEntity();
+ entity.UpdatingEntity();
var dto = ExternalLoginFactory.BuildDto(entity);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
index 5a62c25df7..1a8b2b8821 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
@@ -102,17 +102,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override IEnumerable GetDeleteClauses()
{
-
var list = new List
{
//NOTE: There is no constraint between the Language and cmsDictionary/cmsLanguageText tables (?)
// but we still need to remove them
- "DELETE FROM cmsLanguageText WHERE languageId = @id",
- "DELETE FROM umbracoPropertyData WHERE languageId = @id",
- "DELETE FROM umbracoContentVersionCultureVariation WHERE languageId = @id",
- "DELETE FROM umbracoDocumentCultureVariation WHERE languageId = @id",
- "DELETE FROM umbracoLanguage WHERE id = @id",
- "DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id"
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.DictionaryValue + " WHERE languageId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE languageId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE languageId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentCultureVariation + " WHERE languageId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.TagRelationship + " WHERE tagId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id)",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id",
+ "DELETE FROM " + Constants.DatabaseSchema.Tables.Language + " WHERE id = @id"
};
return list;
}
@@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace())
throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name.");
- ((EntityBase) entity).AddingEntity();
+ entity.AddingEntity();
// deal with entity becoming the new default entity
if (entity.IsDefault)
@@ -156,7 +156,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace())
throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name.");
- ((EntityBase) entity).UpdatingEntity();
+ entity.UpdatingEntity();
if (entity.IsDefault)
{
@@ -250,7 +250,6 @@ 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/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs
index 565917e078..f0044e225d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs
@@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IMacro entity)
{
- ((EntityBase)entity).AddingEntity();
+ entity.AddingEntity();
var dto = MacroFactory.BuildDto(entity);
@@ -152,7 +152,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IMacro entity)
{
- ((EntityBase)entity).UpdatingEntity();
+ entity.UpdatingEntity();
;
var dto = MacroFactory.BuildDto(entity);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
index 65043c4c67..25828b8126 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
@@ -8,6 +8,7 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
@@ -217,7 +218,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IMedia entity)
{
var media = (Models.Media) entity;
- media.AddingEntity();
+ entity.AddingEntity();
// ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs
index 281255e755..512011b52c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs
@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
@@ -16,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class MediaTypeRepository : ContentTypeRepositoryBase, IMediaTypeRepository
{
- public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
- : base(scopeAccessor, cache, logger, commonRepository)
+ public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
+ : base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
@@ -49,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
- if (ids.Any()) throw new Exception("panic");
+ if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType();
}
@@ -102,7 +104,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IMediaType entity)
{
- ((MediaType)entity).AddingEntity();
+ entity.AddingEntity();
PersistNewBaseContentType(entity);
@@ -114,7 +116,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ValidateAlias(entity);
//Updates Modified date
- ((MediaType)entity).UpdatingEntity();
+ entity.UpdatingEntity();
//Look up parent to get and set the correct Path if ParentId has changed
if (entity.IsPropertyDirty("ParentId"))
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs
index ff7a79f98e..c138550de5 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs
@@ -6,6 +6,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
@@ -91,8 +92,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IMemberGroup entity)
{
//Save to db
+ entity.AddingEntity();
var group = (MemberGroup)entity;
- group.AddingEntity();
var dto = MemberGroupFactory.BuildDto(group);
var o = Database.IsNew(dto) ? Convert.ToInt32(Database.Insert(dto)) : Database.Update(dto);
group.Id = dto.NodeId; //Set Id on entity to ensure an Id is set
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
index 808f61305a..1fc3568fc0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
@@ -6,6 +6,7 @@ using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
@@ -232,8 +233,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IMember entity)
{
+ if (entity.ProviderUserKey == null)
+ {
+ entity.ProviderUserKey = entity.Key;
+ }
+ entity.AddingEntity();
+
var member = (Member) entity;
- member.AddingEntity();
// ensure that strings don't contain characters that are invalid in xml
// TODO: do we really want to keep doing this here?
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs
index a32ec1b422..ee651819bf 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs
@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
@@ -17,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class MemberTypeRepository : ContentTypeRepositoryBase, IMemberTypeRepository
{
- public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
- : base(scopeAccessor, cache, logger, commonRepository)
+ public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
+ : base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
@@ -56,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
- if (ids.Any()) throw new Exception("panic");
+ if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType();
}
@@ -131,12 +133,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
ValidateAlias(entity);
- ((MemberType)entity).AddingEntity();
+ entity.AddingEntity();
//set a default icon if one is not specified
if (entity.Icon.IsNullOrWhiteSpace())
{
- entity.Icon = "icon-user";
+ entity.Icon = Constants.Icons.Member;
}
//By Convention we add 9 standard PropertyTypes to an Umbraco MemberType
@@ -165,7 +167,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ValidateAlias(entity);
//Updates Modified date
- ((MemberType)entity).UpdatingEntity();
+ entity.UpdatingEntity();
//Look up parent to get and set the correct Path if ParentId has changed
if (entity.IsPropertyDirty("ParentId"))
@@ -224,6 +226,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
//this reset's its current data type reference which will be re-assigned based on the property editor assigned on the next line
propertyType.DataTypeId = 0;
+ propertyType.DataTypeKey = default;
}
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs
index bd2580b38f..1dc7aa478d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs
@@ -5,6 +5,7 @@ using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
index c5ba24f385..4b4af505b8 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
@@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IRelation entity)
{
- ((EntityBase)entity).AddingEntity();
+ entity.AddingEntity();
var factory = new RelationFactory(entity.RelationType);
var dto = factory.BuildDto(entity);
@@ -147,7 +147,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IRelation entity)
{
- ((EntityBase)entity).UpdatingEntity();
+ entity.UpdatingEntity();
var factory = new RelationFactory(entity.RelationType);
var dto = factory.BuildDto(entity);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs
index 4faf78bd0a..075d4aa769 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs
@@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IRelationType entity)
{
- ((EntityBase)entity).AddingEntity();
+ entity.AddingEntity();
var dto = RelationTypeFactory.BuildDto(entity);
@@ -145,7 +145,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IRelationType entity)
{
- ((EntityBase)entity).UpdatingEntity();
+ entity.UpdatingEntity();
var dto = RelationTypeFactory.BuildDto(entity);
Database.Update(dto);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs
index 6b2dfddaeb..1497c2857c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs
@@ -5,6 +5,7 @@ using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
@@ -96,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IServerRegistration entity)
{
- ((ServerRegistration)entity).AddingEntity();
+ entity.AddingEntity();
var dto = ServerRegistrationFactory.BuildDto(entity);
@@ -108,7 +109,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IServerRegistration entity)
{
- ((ServerRegistration)entity).UpdatingEntity();
+ entity.UpdatingEntity();
var dto = ServerRegistrationFactory.BuildDto(entity);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs
index f26fcca81b..279e03b19e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs
@@ -85,7 +85,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
protected override void PersistNewItem(ITag entity)
{
- ((EntityBase)entity).AddingEntity();
+ entity.AddingEntity();
var dto = TagFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
@@ -97,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
protected override void PersistUpdatedItem(ITag entity)
{
- ((EntityBase)entity).UpdatingEntity();
+ entity.UpdatingEntity();
var dto = TagFactory.BuildDto(entity);
Database.Update(dto);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs
index 3b247950e4..0701a0996e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs
@@ -290,7 +290,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IUserGroup entity)
{
- ((UserGroup) entity).AddingEntity();
+ entity.AddingEntity();
var userGroupDto = UserGroupFactory.BuildDto(entity);
@@ -304,7 +304,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IUserGroup entity)
{
- ((UserGroup) entity).UpdatingEntity();
+ entity.UpdatingEntity();
var userGroupDto = UserGroupFactory.BuildDto(entity);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs
index 91a20c5bdd..96abc37662 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs
@@ -445,7 +445,7 @@ ORDER BY colName";
return;
}
- ((User) entity).AddingEntity();
+ entity.AddingEntity();
// ensure security stamp if missing
if (entity.SecurityStamp.IsNullOrWhiteSpace())
@@ -495,7 +495,7 @@ ORDER BY colName";
protected override void PersistUpdatedItem(IUser entity)
{
// updates Modified date
- ((User) entity).UpdatingEntity();
+ entity.UpdatingEntity();
// ensure security stamp if missing
if (entity.SecurityStamp.IsNullOrWhiteSpace())
diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs
index 9471285148..afd602cfc9 100644
--- a/src/Umbraco.Core/Properties/AssemblyInfo.cs
+++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs
@@ -32,7 +32,7 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Umbraco.Forms.Web")]
// Umbraco Headless
-[assembly: InternalsVisibleTo("Umbraco.Headless")]
+[assembly: InternalsVisibleTo("Umbraco.Cloud.Headless")]
// code analysis
// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it
diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldsExtensions.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldsExtensions.cs
new file mode 100644
index 0000000000..25fba622d5
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldsExtensions.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.PropertyEditors
+{
+ public static partial class ConfigurationFieldsExtensions
+ {
+ ///
+ /// Adds a configuration field.
+ ///
+ /// The list of configuration fields.
+ /// The key (alias) of the field.
+ /// The name (label) of the field.
+ /// The description for the field.
+ /// The path to the editor view to be used for the field.
+ /// Optional configuration used for field's editor.
+ public static void Add(
+ this List fields,
+ string key,
+ string name,
+ string description,
+ string view,
+ IDictionary config = null)
+ {
+ fields.Add(new ConfigurationField
+ {
+ Key = key,
+ Name = name,
+ Description = description,
+ View = view,
+ Config = config,
+ });
+ }
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
index 43f4b68b99..dbb2fc467e 100644
--- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
@@ -30,7 +30,7 @@ namespace Umbraco.Core.PropertyEditors
// defaults
Type = type;
Icon = Constants.Icons.PropertyEditor;
- Group = "common";
+ Group = Constants.PropertyEditors.Groups.Common;
// assign properties based on the attribute, if it is found
Attribute = GetType().GetCustomAttribute(false);
diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs
index ca08127d51..2639b627a9 100644
--- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs
@@ -111,6 +111,11 @@ namespace Umbraco.Core.PropertyEditors
///
public bool HideLabel { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the editor value can be copied
+ ///
+ public bool CanCopy { get; set; }
+
///
/// Gets or sets an optional icon.
///
@@ -121,7 +126,7 @@ namespace Umbraco.Core.PropertyEditors
/// Gets or sets an optional group.
///
/// The group can be used for example to group the editors by category.
- public string Group { get; set; } = "common";
+ public string Group { get; set; } = Constants.PropertyEditors.Groups.Common;
///
/// Gets or sets a value indicating whether the value editor is deprecated.
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
index b88c49583f..f1c76c2b8b 100644
--- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
@@ -57,6 +57,7 @@ namespace Umbraco.Core.PropertyEditors
View = view;
ValueType = attribute.ValueType;
HideLabel = attribute.HideLabel;
+ CanCopy = attribute.CanCopy;
}
///
@@ -133,6 +134,12 @@ namespace Umbraco.Core.PropertyEditors
[JsonProperty("hideLabel")]
public bool HideLabel { get; set; }
+ ///
+ /// If this is true then the editor value can be copied
+ ///
+ [JsonProperty("canCopy")]
+ public bool CanCopy { get; set; }
+
///
/// Set this to true if the property editor is for display purposes only
///
diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs
index 0db4881da4..e31015f60c 100644
--- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs
@@ -34,8 +34,14 @@ namespace Umbraco.Core.PropertyEditors
///
bool HideLabel { get; }
+ ///
+ /// Gets a value indicating whether the value can be copied.
+ ///
+ bool CanCopy { get; }
+
///
/// Validates a property value using custom messages.
+
///
/// The property value.
/// A value indicating whether the property value is required.
diff --git a/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs
new file mode 100644
index 0000000000..bef3f42f46
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs
@@ -0,0 +1,10 @@
+namespace Umbraco.Core.PropertyEditors
+{
+ ///
+ /// Marker interface for any editor configuration that supports Ignoring user start nodes
+ ///
+ internal interface IIgnoreUserStartNodesConfig
+ {
+ bool IgnoreUserStartNodes { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs
index d0c40b1e63..60b7d55c01 100644
--- a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs
@@ -5,7 +5,11 @@ namespace Umbraco.Core.PropertyEditors
///
/// Represents a property editor for label properties.
///
- [DataEditor(Constants.PropertyEditors.Aliases.Label, "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/TagsPropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs
index 2439c7c02e..6549f1a233 100644
--- a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs
+++ b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs
@@ -55,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors
///
/// Gets the type of the dynamic configuration provider.
///
+ //TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562
public Type TagsConfigurationProviderType { get; }
}
}
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs
index 84baf226cf..eb461b4920 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs
@@ -65,6 +65,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
if (source is decimal sourceDecimal) return sourceDecimal;
if (source is string sourceDecimalString)
return decimal.TryParse(sourceDecimalString, NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : 0;
+ if (source is double sourceDouble)
+ return Convert.ToDecimal(sourceDouble);
return (decimal) 0;
case ValueTypes.Integer:
if (source is int sourceInt) return sourceInt;
diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs
index f220f307d6..921883b822 100644
--- a/src/Umbraco.Core/PublishedContentExtensions.cs
+++ b/src/Umbraco.Core/PublishedContentExtensions.cs
@@ -103,18 +103,26 @@ namespace Umbraco.Core
/// Gets the children of the content item.
///
/// The content item.
- /// The specific culture to get the url children for. If null is used the current culture is used (Default is null).
+ ///
+ /// The specific culture to get the url children for. Default is null which will use the current culture in
+ ///
///
/// Gets children that are available for the specified culture.
/// Children are sorted by their sortOrder.
- /// The '*' culture and supported and returns everything.
+ ///
+ /// For culture,
+ /// if null is used the current culture is used.
+ /// If an empty string is used only invariant children are returned.
+ /// If "*" is used all children are returned.
+ ///
+ ///
+ /// If a variant culture is specified or there is a current culture in the then the Children returned
+ /// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture.
+ /// However, if an empty string is specified only invariant children are returned.
+ ///
///
public static IEnumerable Children(this IPublishedContent content, string culture = null)
{
- // invariant has invariant value (whatever the requested culture)
- if (!content.ContentType.VariesByCulture() && culture != "*")
- culture = "";
-
// handle context culture for variant
if (culture == null)
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
index 4e026490a4..ac8c9f1be7 100644
--- a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
+++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core
@@ -9,17 +11,21 @@ namespace Umbraco.Core
public static class PublishedModelFactoryExtensions
{
///
- /// Executes an action with a safe live factory/
+ /// Returns true if the current is an implementation of
///
- ///
- /// If the factory is a live factory, ensures it is refreshed and locked while executing the action.
- ///
+ ///
+ ///
+ public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
+
+ [Obsolete("This method is no longer used or necessary and will be removed from future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action)
{
if (factory is ILivePublishedModelFactory liveFactory)
{
lock (liveFactory.SyncRoot)
{
+ //Call refresh on the live factory to re-compile the models
liveFactory.Refresh();
action();
}
@@ -29,5 +35,38 @@ namespace Umbraco.Core
action();
}
}
+
+ ///
+ /// Sets a flag to reset the ModelsBuilder models if the is
+ ///
+ ///
+ ///
+ ///
+ /// This does not recompile the pure live models, only sets a flag to tell models builder to recompile when they are requested.
+ ///
+ internal static void WithSafeLiveFactoryReset(this IPublishedModelFactory factory, Action action)
+ {
+ if (factory is ILivePublishedModelFactory liveFactory)
+ {
+ lock (liveFactory.SyncRoot)
+ {
+ // TODO: Fix this in 8.3! - We need to change the ILivePublishedModelFactory interface to have a Reset method and then when we have an embedded MB
+ // version we will publicize the ResetModels (and change the name to Reset).
+ // For now, this will suffice and we'll use reflection, there should be no other implementation of ILivePublishedModelFactory.
+ // Calling ResetModels resets the MB flag so that the next time EnsureModels is called (which is called when nucache lazily calls CreateModel) it will
+ // trigger the recompiling of pure live models.
+ var resetMethod = liveFactory.GetType().GetMethod("ResetModels", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
+ if (resetMethod != null)
+ resetMethod.Invoke(liveFactory, null);
+
+ action();
+ }
+ }
+ else
+ {
+ action();
+ }
+ }
+
}
}
diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
index f32a46f3f8..1f004846d0 100644
--- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
+++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
@@ -58,7 +58,7 @@ namespace Umbraco.Core.Runtime
composition.RegisterUnique();
// register our predefined validators
- composition.WithCollectionBuilder()
+ composition.ManifestValueValidators()
.Add()
.Add()
.Add()
@@ -66,6 +66,9 @@ namespace Umbraco.Core.Runtime
.Add()
.Add();
+ // register the manifest filter collection builder (collection is empty by default)
+ composition.ManifestFilters();
+
// properties and parameters derive from data editors
composition.WithCollectionBuilder()
.Add(() => composition.TypeLoader.GetDataEditors());
diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs
index f9a41b4f66..5b069641c4 100644
--- a/src/Umbraco.Core/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs
@@ -139,7 +139,7 @@ namespace Umbraco.Core.Runtime
// there should be none, really - this is here "just in case"
Compose(composition);
- // acquire the main domain
+ // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom(mainDom);
// determine our runtime level
@@ -218,13 +218,13 @@ namespace Umbraco.Core.Runtime
IOHelper.SetRootDirectory(path);
}
- private void AcquireMainDom(MainDom mainDom)
+ private bool AcquireMainDom(MainDom mainDom)
{
using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired."))
{
try
{
- mainDom.Acquire();
+ return mainDom.Acquire();
}
catch
{
diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs
index 6fb8a04c0d..5d34fe70a1 100644
--- a/src/Umbraco.Core/RuntimeState.cs
+++ b/src/Umbraco.Core/RuntimeState.cs
@@ -97,6 +97,11 @@ namespace Umbraco.Core
///
internal void EnsureApplicationUrl(HttpRequestBase request = null)
{
+ //Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that
+ // it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part
+ // about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626
+
+
// see U4-10626 - in some cases we want to reset the application url
// (this is a simplified version of what was in 7.x)
// note: should this be optional? is it expensive?
diff --git a/src/Umbraco.Core/Serialization/JsonNetSerializer.cs b/src/Umbraco.Core/Serialization/JsonNetSerializer.cs
index 1b5a9c6caf..46f23ca35e 100644
--- a/src/Umbraco.Core/Serialization/JsonNetSerializer.cs
+++ b/src/Umbraco.Core/Serialization/JsonNetSerializer.cs
@@ -60,7 +60,7 @@ namespace Umbraco.Core.Serialization
///
public IStreamedResult ToStream(object input)
{
- string s = JsonConvert.SerializeObject(input, Formatting.Indented, _settings);
+ string s = JsonConvert.SerializeObject(input, Formatting.None, _settings);
byte[] bytes = Encoding.UTF8.GetBytes(s);
MemoryStream ms = new MemoryStream(bytes);
diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
index 497f7d47a9..bf7f87fd1a 100644
--- a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
+++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
@@ -6,9 +6,25 @@ namespace Umbraco.Core.Services.Changes
public enum ContentTypeChangeTypes : byte
{
None = 0,
- Create = 1, // item type has been created, no impact
- RefreshMain = 2, // changed, impacts content (adding property or composition does NOT)
- RefreshOther = 4, // changed, other changes
- Remove = 8 // item type has been removed
+
+ ///
+ /// Item type has been created, no impact
+ ///
+ Create = 1,
+
+ ///
+ /// Content type changes impact only the Content type being saved
+ ///
+ RefreshMain = 2,
+
+ ///
+ /// Content type changes impacts the content type being saved and others used that are composed of it
+ ///
+ RefreshOther = 4, // changed, other change
+
+ ///
+ /// Content type was removed
+ ///
+ Remove = 8
}
}
diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
index 1175df81dc..dfe02ba690 100644
--- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text.RegularExpressions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
@@ -11,6 +12,42 @@ namespace Umbraco.Core.Services
///
public static class ContentServiceExtensions
{
+ #region RTE Anchor values
+
+ private static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled);
+
+ internal static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id, string culture = "*")
+ {
+ var result = new List();
+ var content = contentService.GetById(id);
+
+ foreach (var contentProperty in content.Properties)
+ {
+ if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce))
+ {
+ var value = contentProperty.GetValue(culture)?.ToString();
+ if (!string.IsNullOrEmpty(value))
+ {
+ result.AddRange(contentService.GetAnchorValuesFromRTEContent(value));
+ }
+ }
+ }
+ return result;
+ }
+
+
+ internal static IEnumerable GetAnchorValuesFromRTEContent(this IContentService contentService, string rteContent)
+ {
+ var result = new List();
+ var matches = AnchorRegex.Matches(rteContent);
+ foreach (Match match in matches)
+ {
+ result.Add(match.Value.Split('\"')[1]);
+ }
+ return result;
+ }
+ #endregion
+
public static IEnumerable GetByIds(this IContentService contentService, IEnumerable ids)
{
var guids = new List();
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
index f643039dca..37f1e5127f 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
@@ -7,6 +7,21 @@ namespace Umbraco.Core.Services
{
public static class ContentTypeServiceExtensions
{
+ ///
+ /// Gets all of the element types (e.g. content types that have been marked as an element type).
+ ///
+ /// The content type service.
+ /// Returns all the element types.
+ public static IEnumerable GetAllElementTypes(this IContentTypeService contentTypeService)
+ {
+ if (contentTypeService == null)
+ {
+ return Enumerable.Empty();
+ }
+
+ return contentTypeService.GetAll().Where(x => x.IsElement);
+ }
+
///
/// Returns the available composite content types for a given content type
///
@@ -22,12 +37,14 @@ namespace Umbraco.Core.Services
/// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
/// be looked up via the db, they need to be passed in.
///
+ /// Wether the composite content types should be applicable for an element type
///
internal static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(this IContentTypeService ctService,
IContentTypeComposition source,
IContentTypeComposition[] allContentTypes,
string[] filterContentTypes = null,
- string[] filterPropertyTypes = null)
+ string[] filterPropertyTypes = null,
+ bool isElement = false)
{
filterContentTypes = filterContentTypes == null
? Array.Empty()
@@ -46,7 +63,7 @@ namespace Umbraco.Core.Services
.Select(c => c.Alias)
.Union(filterPropertyTypes)
.ToArray();
-
+
var sourceId = source?.Id ?? 0;
// find out if any content type uses this content type
@@ -64,8 +81,9 @@ namespace Umbraco.Core.Services
x => x.Id));
// usable types are those that are top-level
+ // do not allow element types to be composed by non-element types as this will break the model generation in ModelsBuilder
var usableContentTypes = allContentTypes
- .Where(x => x.ContentTypeComposition.Any() == false).ToArray();
+ .Where(x => x.ContentTypeComposition.Any() == false && (isElement == false || x.IsElement)).ToArray();
foreach (var x in usableContentTypes)
list.Add(x);
diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs
new file mode 100644
index 0000000000..3b72a6f258
--- /dev/null
+++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs
@@ -0,0 +1,21 @@
+using System;
+using Umbraco.Core.Models;
+using Umbraco.Core.PropertyEditors;
+
+namespace Umbraco.Core.Services
+{
+ internal static class DateTypeServiceExtensions
+ {
+ public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key)
+ {
+ if (DataTypeExtensions.IsBuildInDataType(key)) return false; //built in ones can never be ignoring start nodes
+
+ var dataType = dataTypeService.GetDataType(key);
+
+ if (dataType != null && dataType.Configuration is IIgnoreUserStartNodesConfig ignoreStartNodesConfig)
+ return ignoreStartNodesConfig.IgnoreUserStartNodes;
+
+ return false;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index 48e577a8f0..6f9ca58821 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -526,5 +526,6 @@ namespace Umbraco.Core.Services
OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId);
#endregion
+
}
}
diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs
index 3dc530e250..bb56e110cd 100644
--- a/src/Umbraco.Core/Services/IDataTypeService.cs
+++ b/src/Umbraco.Core/Services/IDataTypeService.cs
@@ -4,6 +4,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
+
///
/// Defines the DataType Service, which is an easy access to operations involving
///
diff --git a/src/Umbraco.Core/Services/Implement/AuditService.cs b/src/Umbraco.Core/Services/Implement/AuditService.cs
index 46c851a789..5eb08f2dea 100644
--- a/src/Umbraco.Core/Services/Implement/AuditService.cs
+++ b/src/Umbraco.Core/Services/Implement/AuditService.cs
@@ -51,8 +51,8 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var result = sinceDate.HasValue == false
- ? _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type))
- : _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value));
+ ? _auditRepository.Get(type, Query().Where(x => x.UserId == userId))
+ : _auditRepository.Get(type, Query().Where(x => x.UserId == userId && x.CreateDate >= sinceDate.Value));
scope.Complete();
return result;
}
@@ -63,8 +63,8 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var result = sinceDate.HasValue == false
- ? _auditRepository.Get(Query().Where(x => x.AuditType == type))
- : _auditRepository.Get(Query().Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value));
+ ? _auditRepository.Get(type, Query())
+ : _auditRepository.Get(type, Query().Where(x => x.CreateDate >= sinceDate.Value));
scope.Complete();
return result;
}
diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index c79e7aa869..ce63be9f2c 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -11,7 +11,6 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
-using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services.Changes;
@@ -31,6 +30,7 @@ namespace Umbraco.Core.Services.Implement
private IQuery _queryNotTrashed;
//TODO: The non-lazy object should be injected
private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService());
+
#region Constructors
@@ -755,11 +755,11 @@ namespace Umbraco.Core.Services.Implement
{
var publishedState = content.PublishedState;
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
- throw new InvalidOperationException("Cannot save (un)publishing content, use the dedicated SavePublished method.");
+ throw new InvalidOperationException($"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method.");
if (content.Name != null && content.Name.Length > 255)
{
- throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ throw new InvalidOperationException($"Content with the name {content.Name} cannot be more than 255 characters in length.");
}
var evtMsgs = EventMessagesFactory.Get();
@@ -884,6 +884,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -892,13 +894,13 @@ namespace Umbraco.Core.Services.Implement
// if culture is '*', then publish them all (including variants)
//this will create the correct culture impact even if culture is * or null
- var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
+ var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
content.PublishCulture(impact);
- var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -919,6 +921,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
var evtMsgs = EventMessagesFactory.Get();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
@@ -926,25 +930,23 @@ namespace Umbraco.Core.Services.Implement
var varies = content.ContentType.VariesByCulture();
- if (cultures.Length == 0)
+ if (cultures.Length == 0 && !varies)
{
//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);
+ return SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents);
}
if (cultures.Any(x => x == null || x == "*"))
throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
- var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x)));
+ var impacts = cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x)));
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
foreach (var impact in impacts)
content.PublishCulture(impact);
- var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -984,6 +986,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -991,26 +995,39 @@ namespace Umbraco.Core.Services.Implement
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
+ // It's important to understand that when the document varies by culture but the "*" is used,
+ // we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected
+ // because we don't want to actually unpublish every culture and then the document, we just want everything
+ // to be non-routable so that when it's re-published all variants were as they were.
+
content.PublishedState = PublishedState.Unpublishing;
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
+ scope.Complete();
+ return result;
}
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;
+ // Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
+ // essentially be re-publishing the document with the requested culture removed.
+ // The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
+ // and will then unpublish the document accordingly.
+ // If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
+ var removed = content.UnpublishCulture(culture);
- if (persisted != null && !persisted.IsCulturePublished(culture))
+ //save and publish any changes
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
+
+ scope.Complete();
+
+ // In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures
+ // were specified to be published which will be the case when removed is false. In that case
+ // we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before).
+ if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed)
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
- // unpublish the culture
- content.UnpublishCulture(culture);
+ return result;
}
-
- var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
- scope.Complete();
- return result;
}
-
}
///
@@ -1045,15 +1062,35 @@ namespace Umbraco.Core.Services.Implement
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);
+ var allLangs = _languageRepository.GetMany().ToList();
+
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
}
+ ///
+ /// Handles a lot of business logic cases for how the document should be persisted
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for pending scheduled publishing, etc... is dealt with in this method.
+ /// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled saving/publishing, branch saving/publishing, etc...
+ ///
+ ///
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
- ContentSavingEventArgs saveEventArgs,
- int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
+ ContentSavingEventArgs saveEventArgs, IReadOnlyCollection allLangs,
+ int userId = Constants.Security.SuperUserId,
+ bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
{
if (scope == null) throw new ArgumentNullException(nameof(scope));
if (content == null) throw new ArgumentNullException(nameof(content));
@@ -1068,8 +1105,8 @@ namespace Umbraco.Core.Services.Implement
if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing)
content.PublishedState = PublishedState.Publishing;
- // state here is either Publishing or Unpublishing
- // (even though, Publishing to unpublish a culture may end up unpublishing everything)
+ // State here is either Publishing or Unpublishing
+ // Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
var publishing = content.PublishedState == PublishedState.Publishing;
var unpublishing = content.PublishedState == PublishedState.Unpublishing;
@@ -1086,6 +1123,18 @@ namespace Umbraco.Core.Services.Implement
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
var previouslyPublished = content.HasIdentity && content.Published;
+ //inline method to persist the document with the documentRepository since this logic could be called a couple times below
+ void SaveDocument(IContent c)
+ {
+ // save, always
+ if (c.HasIdentity == false)
+ c.CreatorId = userId;
+ c.WriterId = userId;
+
+ // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
+ _documentRepository.Save(c);
+ }
+
if (publishing)
{
//determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
@@ -1095,11 +1144,25 @@ namespace Umbraco.Core.Services.Implement
: null;
// ensure that the document can be published, and publish handling events, business rules, etc
- publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs);
+ publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs);
if (publishResult.Success)
{
// note: StrategyPublish flips the PublishedState to Publishing!
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs);
+
+ //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
+ if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0)
+ {
+ // This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
+ // so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
+ // the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
+ // persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
+ // mark the document for Unpublishing.
+ SaveDocument(content);
+
+ //set the flag to unpublish and continue
+ unpublishing = content.Published; // if not published yet, nothing to do
+ }
}
else
{
@@ -1160,13 +1223,8 @@ namespace Umbraco.Core.Services.Implement
}
}
- // save, always
- if (content.HasIdentity == false)
- content.CreatorId = userId;
- content.WriterId = userId;
-
- // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
- _documentRepository.Save(content);
+ //Persist the document
+ SaveDocument(content);
// raise the Saved event, always
if (raiseEvents)
@@ -1184,17 +1242,34 @@ namespace Umbraco.Core.Services.Implement
if (culturesUnpublishing != null)
{
- //If we are here, it means we tried unpublishing a culture but it was mandatory so now everything is unpublished
- var langs = string.Join(", ", _languageRepository.GetMany()
+ // This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
+
+ var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
- //log that the whole content item has been unpublished due to mandatory culture unpublished
- Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
- }
- else
- Audit(AuditType.Unpublish, userId, content.Id);
+ if (publishResult == null)
+ throw new PanicException("publishResult == null - should not happen");
+
+ switch(publishResult.Result)
+ {
+ case PublishResultType.FailedPublishMandatoryCultureMissing:
+ //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
+
+ //log that the whole content item has been unpublished due to mandatory culture unpublished
+ Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
+ return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, evtMsgs, content);
+ case PublishResultType.SuccessUnpublishCulture:
+ //occurs when the last culture is unpublished
+
+ Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)");
+ return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content);
+ }
+
+ }
+
+ Audit(AuditType.Unpublish, userId, content.Id);
return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
}
@@ -1209,6 +1284,9 @@ namespace Umbraco.Core.Services.Implement
{
if (isNew == false && previouslyPublished == false)
changeType = TreeChangeTypes.RefreshBranch; // whole branch
+ else if (isNew == false && previouslyPublished)
+ changeType = TreeChangeTypes.RefreshNode; // single node
+
// invalidate the node/branch
if (!branchOne) // for branches, handled by SaveAndPublishBranch
@@ -1234,7 +1312,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessPublishCulture:
if (culturesPublishing != null)
{
- var langs = string.Join(", ", _languageRepository.GetMany()
+ var langs = string.Join(", ", allLangs
.Where(x => culturesPublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs);
@@ -1243,7 +1321,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessUnpublishCulture:
if (culturesUnpublishing != null)
{
- var langs = string.Join(", ", _languageRepository.GetMany()
+ var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
@@ -1257,14 +1335,14 @@ namespace Umbraco.Core.Services.Implement
// should not happen
if (branchOne && !branchRoot)
- throw new Exception("panic");
+ throw new PanicException("branchOne && !branchRoot - should not happen");
//if publishing didn't happen or if it has failed, we still need to log which cultures were saved
if (!branchOne && (publishResult == null || !publishResult.Success))
{
if (culturesChanging != null)
{
- var langs = string.Join(", ", _languageRepository.GetMany()
+ var langs = string.Join(", ", allLangs
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
@@ -1295,6 +1373,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
foreach (var d in _documentRepository.GetContentForRelease(date))
{
PublishResult result;
@@ -1323,7 +1403,7 @@ namespace Umbraco.Core.Services.Implement
//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 impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture));
+ var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture));
var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
@@ -1338,7 +1418,7 @@ namespace Umbraco.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
- result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
+ result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
@@ -1388,7 +1468,7 @@ namespace Umbraco.Core.Services.Implement
d.UnpublishCulture(c);
}
- result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
+ result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
@@ -1414,7 +1494,7 @@ namespace Umbraco.Core.Services.Implement
}
// utility 'PublishCultures' func used by SaveAndPublishBranch
- private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish)
+ private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish, IReadOnlyCollection allLangs)
{
//TODO: This does not support being able to return invalid property details to bubble up to the UI
@@ -1424,7 +1504,7 @@ namespace Umbraco.Core.Services.Implement
{
return culturesToPublish.All(culture =>
{
- var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
+ var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
});
}
@@ -1533,7 +1613,7 @@ namespace Umbraco.Core.Services.Implement
internal IEnumerable SaveAndPublishBranch(IContent document, bool force,
Func> shouldPublish,
- Func, bool> publishCultures,
+ Func, IReadOnlyCollection, bool> publishCultures,
int userId = Constants.Security.SuperUserId)
{
if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish));
@@ -1547,6 +1627,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
if (!document.HasIdentity)
throw new InvalidOperationException("Cannot not branch-publish a new document.");
@@ -1555,7 +1637,7 @@ namespace Umbraco.Core.Services.Implement
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
// deal with the branch root - if it fails, abort
- var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
+ var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1586,7 +1668,7 @@ namespace Umbraco.Core.Services.Implement
}
// no need to check path here, parent has to be published here
- result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
+ result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1618,10 +1700,10 @@ namespace Umbraco.Core.Services.Implement
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document,
Func> shouldPublish,
- Func, bool> publishCultures,
+ Func, IReadOnlyCollection, bool> publishCultures,
bool isRoot,
ICollection publishedDocuments,
- EventMessages evtMsgs, int userId)
+ EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs)
{
var culturesToPublish = shouldPublish(document);
if (culturesToPublish == null) // null = do not include
@@ -1634,13 +1716,13 @@ namespace Umbraco.Core.Services.Implement
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
// publish & check if values are valid
- if (!publishCultures(document, culturesToPublish))
+ if (!publishCultures(document, culturesToPublish, allLangs))
{
//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 = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
+ var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot);
if (result.Success)
publishedDocuments.Add(document);
return result;
@@ -2341,6 +2423,9 @@ namespace Umbraco.Core.Services.Implement
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Document), message, parameters));
}
+ private bool IsDefaultCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture));
+ private bool IsMandatoryCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
+
#endregion
#region Event Handlers
@@ -2495,7 +2580,9 @@ namespace Umbraco.Core.Services.Implement
///
///
///
- private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
+ private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing,
+ IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs,
+ IReadOnlyCollection allLangs)
{
// raise Publishing event
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
@@ -2507,8 +2594,8 @@ namespace Umbraco.Core.Services.Implement
var variesByCulture = content.ContentType.VariesByCulture();
var impactsToPublish = culturesPublishing == null
- ? new[] {CultureImpact.Invariant} //if it's null it's invariant
- : culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray();
+ ? new[] { CultureImpact.Invariant } //if it's null it's invariant
+ : culturesPublishing.Select(x => CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
// publish the culture(s)
if (!impactsToPublish.All(content.PublishCulture))
@@ -2529,11 +2616,17 @@ namespace Umbraco.Core.Services.Implement
if (culturesPublishing == null)
throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null.");
- if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published
+ if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0)
+ {
+ // no published cultures = cannot be published
+ // This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case
+ // there will be nothing to publish/unpublish.
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
+ }
+
// missing mandatory culture = cannot be published
- var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
+ var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
var mandatoryMissing = mandatoryCultures.Any(x => !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase));
if (mandatoryMissing)
return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
@@ -2674,6 +2767,7 @@ namespace Umbraco.Core.Services.Implement
{
var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
+ //TODO: What is this check?? we just created this attempt and of course it is Success?!
if (attempt.Success == false)
return attempt;
@@ -2892,13 +2986,27 @@ namespace Umbraco.Core.Services.Implement
content.CreatorId = userId;
content.WriterId = userId;
+ IEnumerable cultures = ArrayOfOneNullString;
+ if (blueprint.CultureInfos.Count > 0)
+ {
+ cultures = blueprint.CultureInfos.Values.Select(x => x.Culture);
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(), out var defaultCulture))
+ {
+ defaultCulture.Name = name;
+ }
+
+ scope.Complete();
+ }
+ }
+
var now = DateTime.Now;
- 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)
{
- var propertyCulture = property.PropertyType.VariesByCulture() ? culture : null;
+ var propertyCulture = property.PropertyType.VariesByCulture() ? culture : null;
content.SetValue(property.Alias, property.GetValue(propertyCulture), propertyCulture);
}
@@ -3024,5 +3132,8 @@ namespace Umbraco.Core.Services.Implement
}
#endregion
+
+
+
}
}
diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index 1f1f0d9ac3..6ac8e1404a 100644
--- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -390,6 +390,11 @@ namespace Umbraco.Core.Services.Implement
if (string.IsNullOrWhiteSpace(item.Name))
throw new ArgumentException("Cannot save item with empty name.");
+ if (item.Name != null && item.Name.Length > 255)
+ {
+ throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ }
+
scope.WriteLock(WriteLockIds);
// validate the DAG transform, within the lock
diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs
index 3552b2d8fc..dc998b18dd 100644
--- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs
+++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs
@@ -349,6 +349,11 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentException("Cannot save datatype with empty name.");
}
+ if (dataType.Name != null && dataType.Name.Length > 255)
+ {
+ throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ }
+
_dataTypeRepository.Save(dataType);
saveEventArgs.CanCancel = false;
diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
index d9be4b01b7..5189b3422e 100644
--- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
+++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
@@ -172,7 +172,7 @@ namespace Umbraco.Core.Services.Implement
xml.Add(new XAttribute("Id", dataType.EditorAlias));
xml.Add(new XAttribute("Definition", dataType.Key));
xml.Add(new XAttribute("DatabaseType", dataType.DatabaseType.ToString()));
- xml.Add(new XAttribute("Configuration", JsonConvert.SerializeObject(dataType.Configuration)));
+ xml.Add(new XAttribute("Configuration", JsonConvert.SerializeObject(dataType.Configuration, PropertyEditors.ConfigurationEditor.ConfigurationJsonSettings)));
var folderNames = string.Empty;
if (dataType.Level != 1)
@@ -439,7 +439,8 @@ namespace Umbraco.Core.Services.Implement
new XElement("Description", contentType.Description),
new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
new XElement("IsListView", contentType.IsContainer.ToString()),
- new XElement("IsElement", contentType.IsElement.ToString()));
+ new XElement("IsElement", contentType.IsElement.ToString()),
+ new XElement("Variations", contentType.Variations.ToString()));
var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
if(masterContentType != null)
@@ -491,7 +492,8 @@ namespace Umbraco.Core.Services.Implement
propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null,
propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null,
propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null,
- propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null);
+ propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null,
+ new XElement("Variations", propertyType.Variations.ToString()));
genericProperties.Add(genericProperty);
}
diff --git a/src/Umbraco.Core/Services/Implement/FileService.cs b/src/Umbraco.Core/Services/Implement/FileService.cs
index 79d5b35775..26a24e9b98 100644
--- a/src/Umbraco.Core/Services/Implement/FileService.cs
+++ b/src/Umbraco.Core/Services/Implement/FileService.cs
@@ -358,6 +358,11 @@ namespace Umbraco.Core.Services.Implement
{ "ContentTypeAlias", contentTypeAlias },
};
+ if (contentTypeAlias != null && contentTypeAlias.Length > 255)
+ {
+ throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ }
+
// check that the template hasn't been created on disk before creating the content type
// if it exists, set the new template content to the existing file content
string content = GetViewContent(contentTypeAlias);
@@ -365,7 +370,10 @@ namespace Umbraco.Core.Services.Implement
{
template.Content = content;
}
+
+
+
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs(template, true, evtMsgs, additionalData);
diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs
index ab075c4ade..e9fdedbf33 100644
--- a/src/Umbraco.Core/Services/Implement/MediaService.cs
+++ b/src/Umbraco.Core/Services/Implement/MediaService.cs
@@ -139,6 +139,10 @@ namespace Umbraco.Core.Services.Implement
var parent = parentId > 0 ? GetById(parentId) : null;
if (parentId > 0 && parent == null)
throw new ArgumentException("No media with that id.", nameof(parentId));
+ if (name != null && name.Length > 255)
+ {
+ throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ }
var media = new Models.Media(name, parentId, mediaType);
using (var scope = ScopeProvider.CreateScope())
@@ -168,6 +172,10 @@ namespace Umbraco.Core.Services.Implement
var mediaType = GetMediaType(mediaTypeAlias);
if (mediaType == null)
throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias));
+ if (name != null && name.Length > 255)
+ {
+ throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ }
var media = new Models.Media(name, -1, mediaType);
using (var scope = ScopeProvider.CreateScope())
@@ -202,6 +210,10 @@ namespace Umbraco.Core.Services.Implement
var mediaType = GetMediaType(mediaTypeAlias);
if (mediaType == null)
throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback
+ if (name != null && name.Length > 255)
+ {
+ throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ }
var media = new Models.Media(name, parent, mediaType);
CreateMedia(scope, media, parent, userId, false);
@@ -648,6 +660,11 @@ namespace Umbraco.Core.Services.Implement
if (string.IsNullOrWhiteSpace(media.Name))
throw new ArgumentException("Media has no name.", nameof(media));
+ if (media.Name != null && media.Name.Length > 255)
+ {
+ throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+ }
+
scope.WriteLock(Constants.Locks.MediaTree);
if (media.HasIdentity == false)
media.CreatorId = userId;
@@ -760,7 +777,7 @@ namespace Umbraco.Core.Services.Implement
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
- while(page * pageSize < total)
+ while (page * pageSize < total)
{
//get descendants - ordered from deepest to shallowest
var descendants = GetPagedDescendants(media.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending));
@@ -945,7 +962,7 @@ namespace Umbraco.Core.Services.Implement
// if media was trashed, and since we're not moving to the recycle bin,
// indicate that the trashed status should be changed to false, else just
// leave it unchanged
- var trashed = media.Trashed ? false : (bool?) null;
+ var trashed = media.Trashed ? false : (bool?)null;
PerformMoveLocked(media, parentId, parent, userId, moves, trashed);
scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.RefreshBranch).ToEventArgs());
@@ -1009,7 +1026,7 @@ namespace Umbraco.Core.Services.Implement
private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash)
{
- if (trash.HasValue) ((ContentBase) media).Trashed = trash.Value;
+ if (trash.HasValue) ((ContentBase)media).Trashed = trash.Value;
_mediaRepository.Save(media);
}
diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs
index 1a2b52f9c9..66c1e38267 100644
--- a/src/Umbraco.Core/Services/PublishResultType.cs
+++ b/src/Umbraco.Core/Services/PublishResultType.cs
@@ -49,6 +49,11 @@
///
SuccessUnpublishMandatoryCulture = 6,
+ ///
+ /// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished.
+ ///
+ SuccessUnpublishLastCulture = 8,
+
#endregion
#region Success - Mixed
@@ -113,9 +118,9 @@
FailedPublishContentInvalid = FailedPublish | 8,
///
- /// The document could not be published because it has no publishing flags or values.
+ /// The document could not be published because it has no publishing flags or values or if its a variant document, no cultures were specified to be published.
///
- FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird
+ FailedPublishNothingToPublish = FailedPublish | 9,
///
/// The document could not be published because some mandatory cultures are missing.
diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
index 9cd4a58c65..2a558f85aa 100644
--- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
+++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
@@ -97,8 +97,7 @@ namespace Umbraco.Core.Sync
? ":" + request.ServerVariables["SERVER_PORT"]
: "";
- var useSsl = globalSettings.UseHttps || port == "443";
- var ssl = useSsl ? "s" : ""; // force, whatever the first request
+ var ssl = globalSettings.UseHttps ? "s" : ""; // force, whatever the first request
var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
return url.TrimEnd('/');
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 1c80d6fcc4..e5835dfbfa 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -13,7 +13,7 @@
true
- full
+ portable
false
bin\Debug\
TRACE;DEBUG
@@ -23,7 +23,7 @@
latest
- pdbonly
+ portable
true
bin\Release\
TRACE
@@ -55,6 +55,11 @@
+
+ 1.0.0-beta2-19324-01
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
1.3.0
@@ -199,6 +204,7 @@
+
@@ -219,6 +225,9 @@
+
+
+
@@ -226,6 +235,7 @@
+
@@ -233,7 +243,9 @@
+
+
@@ -256,6 +268,8 @@
+
+
@@ -265,6 +279,7 @@
+
diff --git a/src/Umbraco.Core/WaitHandleExtensions.cs b/src/Umbraco.Core/WaitHandleExtensions.cs
index 0d840a2496..7a9294c113 100644
--- a/src/Umbraco.Core/WaitHandleExtensions.cs
+++ b/src/Umbraco.Core/WaitHandleExtensions.cs
@@ -23,6 +23,8 @@ namespace Umbraco.Core
handle,
(state, timedOut) =>
{
+ //TODO: We aren't checking if this is timed out
+
tcs.SetResult(null);
// we take a lock here to make sure the outer method has completed setting the local variable callbackHandle.
diff --git a/src/Umbraco.Examine/BaseValueSetBuilder.cs b/src/Umbraco.Examine/BaseValueSetBuilder.cs
index 22d379d148..93cee88231 100644
--- a/src/Umbraco.Examine/BaseValueSetBuilder.cs
+++ b/src/Umbraco.Examine/BaseValueSetBuilder.cs
@@ -45,7 +45,7 @@ namespace Umbraco.Examine
continue;
case string strVal:
{
- if (strVal.IsNullOrWhiteSpace()) return;
+ if (strVal.IsNullOrWhiteSpace()) continue;
var key = $"{keyVal.Key}{cultureSuffix}";
if (values.TryGetValue(key, out var v))
values[key] = new List
true
- full
+ portable
false
bin\Debug\
DEBUG;TRACE
@@ -23,7 +23,7 @@
latest
- pdbonly
+ portable
true
bin\Release\
TRACE
@@ -49,6 +49,11 @@
+
+ 1.0.0-beta2-19324-01
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
@@ -67,6 +72,7 @@
+
diff --git a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs
index fed5b9bae7..4a926deebe 100644
--- a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs
+++ b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs
@@ -7,73 +7,24 @@ using Umbraco.Core.Logging;
namespace Umbraco.Examine
{
- public class UmbracoExamineIndexDiagnostics : IIndexDiagnostics
+ public class UmbracoExamineIndexDiagnostics : LuceneIndexDiagnostics
{
private readonly UmbracoExamineIndex _index;
- private readonly ILogger _logger;
public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger)
+ : base(index, logger)
{
_index = index;
- _logger = logger;
}
- public int DocumentCount
+ public override IReadOnlyDictionary Metadata
{
get
{
- try
- {
- return _index.GetIndexDocumentCount();
- }
- catch (AlreadyClosedException)
- {
- _logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
- return 0;
- }
- }
- }
+ var d = base.Metadata.ToDictionary(x => x.Key, x => x.Value);
- public int FieldCount
- {
- get
- {
- try
- {
- return _index.GetIndexFieldCount();
- }
- catch (AlreadyClosedException)
- {
- _logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
- return 0;
- }
- }
- }
-
- public Attempt IsHealthy()
- {
- var isHealthy = _index.IsHealthy(out var indexError);
- return isHealthy ? Attempt.Succeed() : Attempt.Fail(indexError.Message);
- }
-
- public virtual IReadOnlyDictionary Metadata
- {
- get
- {
- var d = new Dictionary
- {
- [nameof(UmbracoExamineIndex.CommitCount)] = _index.CommitCount,
- [nameof(UmbracoExamineIndex.DefaultAnalyzer)] = _index.DefaultAnalyzer.GetType().Name,
- ["LuceneDirectory"] = _index.GetLuceneDirectory().GetType().Name,
- [nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler,
- [nameof(UmbracoExamineIndex.LuceneIndexFolder)] =
- _index.LuceneIndexFolder == null
- ? string.Empty
- : _index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'),
- [nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly,
- //There's too much info here
- //[nameof(UmbracoExamineIndexer.FieldDefinitionCollection)] = _index.FieldDefinitionCollection,
- };
+ d[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler;
+ d[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly;
if (_index.ValueSetValidator is ValueSetValidator vsv)
{
diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
index b9a5890d57..60ef944a8c 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
+++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
@@ -14,7 +14,7 @@
true
- full
+ portable
false
bin\Debug\
DEBUG;TRACE
@@ -22,7 +22,7 @@
4
- pdbonly
+ portable
true
bin\Release\
TRACE
@@ -103,6 +103,11 @@
2.8.0
+
+ 1.0.0-beta2-19324-01
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
diff --git a/src/Umbraco.Tests/Cache/RefresherTests.cs b/src/Umbraco.Tests/Cache/RefresherTests.cs
index b1d0ad7b1a..eb8580c9e2 100644
--- a/src/Umbraco.Tests/Cache/RefresherTests.cs
+++ b/src/Umbraco.Tests/Cache/RefresherTests.cs
@@ -12,20 +12,22 @@ namespace Umbraco.Tests.Cache
[Test]
public void MediaCacheRefresherCanDeserializeJsonPayload()
{
- var source = new[] { new MediaCacheRefresher.JsonPayload(1234, TreeChangeTypes.None) };
+ var source = new[] { new MediaCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None) };
var json = JsonConvert.SerializeObject(source);
var payload = JsonConvert.DeserializeObject(json);
Assert.AreEqual(source[0].Id, payload[0].Id);
+ Assert.AreEqual(source[0].Key, payload[0].Key);
Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes);
}
[Test]
public void ContentCacheRefresherCanDeserializeJsonPayload()
{
- var source = new[] { new ContentCacheRefresher.JsonPayload(1234, TreeChangeTypes.None) };
+ var source = new[] { new ContentCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None) };
var json = JsonConvert.SerializeObject(source);
var payload = JsonConvert.DeserializeObject(json);
Assert.AreEqual(source[0].Id, payload[0].Id);
+ Assert.AreEqual(source[0].Key, payload[0].Key);
Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes);
}
diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs
index 7459ae848b..9cd4f39c17 100644
--- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs
+++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs
@@ -268,7 +268,7 @@ AnotherContentFinder
public void GetDataEditors()
{
var types = _typeLoader.GetDataEditors();
- Assert.AreEqual(38, types.Count());
+ Assert.AreEqual(39, types.Count());
}
///
diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs
index 54080f05de..50ead4b702 100644
--- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs
+++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs
@@ -1,6 +1,4 @@
-using System.Web.Mvc;
-using System.Web.Routing;
-using Moq;
+using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Composing;
@@ -10,6 +8,7 @@ using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Configurations
{
+
[TestFixture]
public class GlobalSettingsTests : BaseWebTest
{
@@ -47,73 +46,18 @@ namespace Umbraco.Tests.Configurations
[TestCase("~/some-wacky/nestedPath", "/MyVirtualDir/NestedVDir/", "some-wacky-nestedpath")]
public void Umbraco_Mvc_Area(string path, string rootPath, string outcome)
{
- var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container
- globalSettingsMock.Setup(x => x.Path).Returns(IOHelper.ResolveUrl(path));
+ var globalSettings = SettingsForTests.GenerateMockGlobalSettings();
+
+ var globalSettingsMock = Mock.Get(globalSettings);
+ globalSettingsMock.Setup(x => x.Path).Returns(() => IOHelper.ResolveUrl(path));
SystemDirectories.Root = rootPath;
- Assert.AreEqual(outcome, Current.Configs.Global().GetUmbracoMvcArea());
+ Assert.AreEqual(outcome, globalSettings.GetUmbracoMvcAreaNoCache());
}
- [TestCase("/umbraco/editContent.aspx")]
- [TestCase("/install/default.aspx")]
- [TestCase("/install/")]
- [TestCase("/install")]
- [TestCase("/install/?installStep=asdf")]
- [TestCase("/install/test.aspx")]
- public void Is_Reserved_Path_Or_Url(string url)
- {
- var globalSettings = TestObjects.GetGlobalSettings();
- Assert.IsTrue(globalSettings.IsReservedPathOrUrl(url));
- }
-
- [TestCase("/base/somebasehandler")]
- [TestCase("/")]
- [TestCase("/home.aspx")]
- [TestCase("/umbraco-test")]
- [TestCase("/install-test")]
- [TestCase("/install.aspx")]
- public void Is_Not_Reserved_Path_Or_Url(string url)
- {
- var globalSettings = TestObjects.GetGlobalSettings();
- Assert.IsFalse(globalSettings.IsReservedPathOrUrl(url));
- }
+
- [TestCase("/Do/Not/match", false)]
- [TestCase("/Umbraco/RenderMvcs", false)]
- [TestCase("/Umbraco/RenderMvc", true)]
- [TestCase("/Umbraco/RenderMvc/Index", true)]
- [TestCase("/Umbraco/RenderMvc/Index/1234", true)]
- [TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
- [TestCase("/api", true)]
- [TestCase("/api/WebApiTest", true)]
- [TestCase("/api/WebApiTest/1234", true)]
- [TestCase("/api/WebApiTest/Index/1234", false)]
- public void Is_Reserved_By_Route(string url, bool shouldMatch)
- {
- //reset the app config, we only want to test routes not the hard coded paths
- var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container
- globalSettingsMock.Setup(x => x.ReservedPaths).Returns("");
- globalSettingsMock.Setup(x => x.ReservedUrls).Returns("");
-
- var routes = new RouteCollection();
-
- routes.MapRoute(
- "Umbraco_default",
- "Umbraco/RenderMvc/{action}/{id}",
- new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional });
- routes.MapRoute(
- "WebAPI",
- "api/{controller}/{id}",
- new { controller = "WebApiTestController", action = "Index", id = UrlParameter.Optional });
-
-
- var context = new FakeHttpContextFactory(url);
-
-
- Assert.AreEqual(
- shouldMatch,
- globalSettingsMock.Object.IsReservedPathOrUrl(url, context.HttpContext, routes));
- }
+
}
}
diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs
index c3708dd8f3..fa9e7b676b 100644
--- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs
+++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs
@@ -639,7 +639,7 @@ namespace Umbraco.Tests.Integration
var m = 0;
Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content.Id}.p+p", _events[i++].ToString());
m++;
- Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content.Id}", _events[i++].ToString());
+ Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshNode/{content.Id}", _events[i++].ToString());
}
[Test]
diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs
index 6605bc4546..1c90f68d62 100644
--- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs
+++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs
@@ -44,7 +44,7 @@ namespace Umbraco.Tests.Manifest
new RequiredValidator(Mock.Of()),
new RegexValidator(Mock.Of(), null)
};
- _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), Mock.Of());
+ _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), Mock.Of());
}
[Test]
diff --git a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs
index abf61370a8..6dd3f2aacb 100644
--- a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs
+++ b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.Packaging;
using Umbraco.Core.Packaging;
using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
using File = System.IO.File;
@@ -45,7 +46,8 @@ namespace Umbraco.Tests.Packaging
Logger, ServiceContext.FileService, ServiceContext.MacroService, ServiceContext.LocalizationService,
ServiceContext.DataTypeService, ServiceContext.EntityService,
ServiceContext.ContentTypeService, ServiceContext.ContentService,
- Factory.GetInstance());
+ Factory.GetInstance(),
+ Factory.GetInstance());
private IPackageInstallation PackageInstallation => new PackageInstallation(
PackageDataInstallation,
diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
index 53f150f140..f953b9cce6 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
@@ -29,10 +29,11 @@ namespace Umbraco.Tests.Persistence.Repositories
private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository)
{
+ var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
- contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
+ contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
@@ -40,9 +41,10 @@ namespace Umbraco.Tests.Persistence.Repositories
private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor)
{
+ var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
- var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
+ var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
@@ -50,7 +52,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
- var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
+ var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
+ var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
index fd797662c0..4d62ec8301 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
@@ -67,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
- contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
+ contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs
index f00b2fd046..628f8d75a7 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs
@@ -23,8 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository);
languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository);
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
return domainRepository;
diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs
index 39f6e3e114..03c1713268 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs
@@ -79,39 +79,6 @@ 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 1d9cf6d022..e2123df9e3 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs
@@ -38,7 +38,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
- mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
+ var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
+ mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of());
return repository;
diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs
index f302d1d992..bb3286daed 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var cacheHelper = AppCaches.Disabled;
var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
- return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger);
+ return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
}
private EntityContainerRepository CreateContainerRepository(IScopeProvider provider)
diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs
index a5f7f08f22..17b16ad7ab 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs
@@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = Mock.Of();
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
+ var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger);
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of());
diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs
index 79e8e43804..4b9f3096ce 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs
@@ -24,7 +24,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = Mock.Of();
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
- return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of());
+ return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository, languageRepository);
}
[Test]
diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs
index 803eff25af..56041c24aa 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs
@@ -308,8 +308,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs
index b6cc4dc50d..e3de2c2892 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs
@@ -956,8 +956,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -968,7 +968,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
- mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
+ var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of());
return repository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs
index 13cbd463fb..b0f9a5335b 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs
@@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches);
- var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
+ var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage");
diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
index b550091591..3e5919d7f3 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
@@ -26,7 +26,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository);
+ var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
+ mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository, languageRepository);
var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of());
return repository;
@@ -44,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs
index 809354bd74..44879eae2f 100644
--- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs
+++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs
@@ -327,7 +327,7 @@ AnotherContentFinder
public void Resolves_RestExtensions()
{
var types = _manager.ResolveRestExtensions();
- Assert.AreEqual(3, types.Count());
+ Assert.AreEqual(2, types.Count());
}
[Test]
diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
index f3a520ead1..ac3ac7d455 100644
--- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
@@ -1,7 +1,9 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
+using System.Reflection;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -25,6 +27,7 @@ using Umbraco.Web.Cache;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
using Umbraco.Web.PublishedCache.NuCache.DataSource;
+using Umbraco.Web.PublishedCache.NuCache.Snap;
namespace Umbraco.Tests.PublishedContent
{
@@ -79,12 +82,16 @@ namespace Umbraco.Tests.PublishedContent
_contentTypeVariant
};
- var contentTypeService = Mock.Of();
- Mock.Get(contentTypeService).Setup(x => x.GetAll()).Returns(contentTypes);
- Mock.Get(contentTypeService).Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes);
+ var contentTypeService = new Mock();
+ contentTypeService.Setup(x => x.GetAll()).Returns(contentTypes);
+ 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 mediaTypeService = new Mock();
+ mediaTypeService.Setup(x => x.GetAll()).Returns(Enumerable.Empty());
+ mediaTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(Enumerable.Empty());
+
+ var contentTypeServiceBaseFactory = new Mock();
+ contentTypeServiceBaseFactory.Setup(x => x.For(It.IsAny())).Returns(contentTypeService.Object);
var dataTypeService = Mock.Of();
Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes);
@@ -94,13 +101,15 @@ namespace Umbraco.Tests.PublishedContent
dataTypeService: dataTypeService,
memberTypeService: Mock.Of(),
memberService: Mock.Of(),
- contentTypeService: contentTypeService,
- localizationService: Mock.Of()
+ contentTypeService: contentTypeService.Object,
+ mediaTypeService: mediaTypeService.Object,
+ localizationService: Mock.Of(),
+ domainService: Mock.Of()
);
// create a scope provider
- var scopeProvider = Mock.Of();
- Mock.Get(scopeProvider)
+ var scopeProvider = new Mock();
+ scopeProvider
.Setup(x => x.CreateScope(
It.IsAny(),
It.IsAny(),
@@ -124,7 +133,7 @@ namespace Umbraco.Tests.PublishedContent
_source = new TestDataSource(kits);
// at last, create the complete NuCache snapshot service!
- var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
+ var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
_snapshotService = new PublishedSnapshotService(options,
null,
runtime,
@@ -133,8 +142,8 @@ namespace Umbraco.Tests.PublishedContent
null,
_snapshotAccessor,
_variationAccesor,
- Mock.Of(),
- scopeProvider,
+ Mock.Of(),
+ scopeProvider.Object,
Mock.Of(),
Mock.Of(),
Mock.Of(),
@@ -151,117 +160,139 @@ namespace Umbraco.Tests.PublishedContent
Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor);
}
+ private IEnumerable GetNestedVariantKits()
+ {
+ var paths = new Dictionary { { -1, "-1" } };
+
+ //1x variant (root)
+ yield return CreateVariantKit(1, -1, 1, paths);
+
+ //1x invariant under root
+ yield return CreateInvariantKit(4, 1, 1, paths);
+
+ //1x variant under root
+ yield return CreateVariantKit(7, 1, 4, paths);
+
+ //2x mixed under invariant
+ yield return CreateVariantKit(10, 4, 1, paths);
+ yield return CreateInvariantKit(11, 4, 2, paths);
+
+ //2x mixed under variant
+ yield return CreateVariantKit(12, 7, 1, paths);
+ yield return CreateInvariantKit(13, 7, 2, paths);
+ }
+
private IEnumerable GetInvariantKits()
{
var paths = new Dictionary { { -1, "-1" } };
- ContentNodeKit CreateKit(int id, int parentId, int sortOrder)
+ yield return CreateInvariantKit(1, -1, 1, paths);
+ yield return CreateInvariantKit(2, -1, 2, paths);
+ yield return CreateInvariantKit(3, -1, 3, paths);
+
+ yield return CreateInvariantKit(4, 1, 1, paths);
+ yield return CreateInvariantKit(5, 1, 2, paths);
+ yield return CreateInvariantKit(6, 1, 3, paths);
+
+ yield return CreateInvariantKit(7, 2, 3, paths);
+ yield return CreateInvariantKit(8, 2, 2, paths);
+ yield return CreateInvariantKit(9, 2, 1, paths);
+
+ yield return CreateInvariantKit(10, 3, 1, paths);
+
+ yield return CreateInvariantKit(11, 4, 1, paths);
+ yield return CreateInvariantKit(12, 4, 2, paths);
+ }
+
+ private ContentNodeKit CreateInvariantKit(int id, int parentId, int sortOrder, Dictionary paths)
+ {
+ if (!paths.TryGetValue(parentId, out var parentPath))
+ throw new Exception("Unknown parent.");
+
+ var path = paths[id] = parentPath + "," + id;
+ var level = path.Count(x => x == ',');
+ var now = DateTime.Now;
+
+ return new ContentNodeKit
{
- if (!paths.TryGetValue(parentId, out var parentPath))
- throw new Exception("Unknown parent.");
-
- var path = paths[id] = parentPath + "," + id;
- var level = path.Count(x => x == ',');
- var now = DateTime.Now;
-
- return new ContentNodeKit
+ ContentTypeId = _contentTypeInvariant.Id,
+ Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
+ DraftData = null,
+ PublishedData = new ContentData
{
- ContentTypeId = _contentTypeInvariant.Id,
- Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
- DraftData = null,
- PublishedData = new ContentData
- {
- Name = "N" + id,
- Published = true,
- TemplateId = 0,
- VersionId = 1,
- VersionDate = now,
- WriterId = 0,
- Properties = new Dictionary(),
- CultureInfos = new Dictionary()
- }
- };
- }
-
- yield return CreateKit(1, -1, 1);
- yield return CreateKit(2, -1, 2);
- yield return CreateKit(3, -1, 3);
-
- yield return CreateKit(4, 1, 1);
- yield return CreateKit(5, 1, 2);
- yield return CreateKit(6, 1, 3);
-
- yield return CreateKit(7, 2, 3);
- yield return CreateKit(8, 2, 2);
- yield return CreateKit(9, 2, 1);
-
- yield return CreateKit(10, 3, 1);
-
- yield return CreateKit(11, 4, 1);
- yield return CreateKit(12, 4, 2);
+ Name = "N" + id,
+ Published = true,
+ TemplateId = 0,
+ VersionId = 1,
+ VersionDate = now,
+ WriterId = 0,
+ Properties = new Dictionary(),
+ CultureInfos = new Dictionary()
+ }
+ };
}
private IEnumerable GetVariantKits()
{
var paths = new Dictionary { { -1, "-1" } };
- Dictionary GetCultureInfos(int id, DateTime now)
+ yield return CreateVariantKit(1, -1, 1, paths);
+ yield return CreateVariantKit(2, -1, 2, paths);
+ yield return CreateVariantKit(3, -1, 3, paths);
+
+ yield return CreateVariantKit(4, 1, 1, paths);
+ yield return CreateVariantKit(5, 1, 2, paths);
+ yield return CreateVariantKit(6, 1, 3, paths);
+
+ yield return CreateVariantKit(7, 2, 3, paths);
+ yield return CreateVariantKit(8, 2, 2, paths);
+ yield return CreateVariantKit(9, 2, 1, paths);
+
+ yield return CreateVariantKit(10, 3, 1, paths);
+
+ yield return CreateVariantKit(11, 4, 1, paths);
+ yield return CreateVariantKit(12, 4, 2, paths);
+ }
+
+ private static Dictionary GetCultureInfos(int id, DateTime now)
+ {
+ var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
+ var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 };
+
+ var infos = new Dictionary();
+ if (en.Contains(id))
+ infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false };
+ if (fr.Contains(id))
+ infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false };
+ return infos;
+ }
+
+ private ContentNodeKit CreateVariantKit(int id, int parentId, int sortOrder, Dictionary paths)
+ {
+ if (!paths.TryGetValue(parentId, out var parentPath))
+ throw new Exception("Unknown parent.");
+
+ var path = paths[id] = parentPath + "," + id;
+ var level = path.Count(x => x == ',');
+ var now = DateTime.Now;
+
+ return new ContentNodeKit
{
- var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
- var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 };
-
- var infos = new Dictionary();
- if (en.Contains(id))
- infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false };
- if (fr.Contains(id))
- infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false };
- return infos;
- }
-
- ContentNodeKit CreateKit(int id, int parentId, int sortOrder)
- {
- if (!paths.TryGetValue(parentId, out var parentPath))
- throw new Exception("Unknown parent.");
-
- var path = paths[id] = parentPath + "," + id;
- var level = path.Count(x => x == ',');
- var now = DateTime.Now;
-
- return new ContentNodeKit
+ ContentTypeId = _contentTypeVariant.Id,
+ Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
+ DraftData = null,
+ PublishedData = new ContentData
{
- ContentTypeId = _contentTypeVariant.Id,
- Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
- DraftData = null,
- PublishedData = new ContentData
- {
- Name = "N" + id,
- Published = true,
- TemplateId = 0,
- VersionId = 1,
- VersionDate = now,
- WriterId = 0,
- Properties = new Dictionary(),
- CultureInfos = GetCultureInfos(id, now)
- }
- };
- }
-
- yield return CreateKit(1, -1, 1);
- yield return CreateKit(2, -1, 2);
- yield return CreateKit(3, -1, 3);
-
- yield return CreateKit(4, 1, 1);
- yield return CreateKit(5, 1, 2);
- yield return CreateKit(6, 1, 3);
-
- yield return CreateKit(7, 2, 3);
- yield return CreateKit(8, 2, 2);
- yield return CreateKit(9, 2, 1);
-
- yield return CreateKit(10, 3, 1);
-
- yield return CreateKit(11, 4, 1);
- yield return CreateKit(12, 4, 2);
+ Name = "N" + id,
+ Published = true,
+ TemplateId = 0,
+ VersionId = 1,
+ VersionDate = now,
+ WriterId = 0,
+ Properties = new Dictionary(),
+ CultureInfos = GetCultureInfos(id, now)
+ }
+ };
}
private IEnumerable GetVariantWithDraftKits()
@@ -427,7 +458,7 @@ namespace Umbraco.Tests.PublishedContent
};
// notify
- _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(10, TreeChangeTypes.RefreshBranch) }, out _, out _);
+ _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(10, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _);
// changes that *I* make are immediately visible on the current snapshot
var documents = snapshot.Content.GetAtRoot().ToArray();
@@ -469,7 +500,7 @@ namespace Umbraco.Tests.PublishedContent
};
// notify
- _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch) }, out _, out _);
+ _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _);
// changes that *I* make are immediately visible on the current snapshot
var documents = snapshot.Content.GetAtRoot().ToArray();
@@ -549,7 +580,7 @@ namespace Umbraco.Tests.PublishedContent
};
// notify
- _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, TreeChangeTypes.RefreshBranch) }, out _, out _);
+ _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _);
// changes that *I* make are immediately visible on the current snapshot
var documents = snapshot.Content.GetById(kit.Node.ParentContentId).Children().ToArray();
@@ -646,8 +677,8 @@ namespace Umbraco.Tests.PublishedContent
_snapshotService.Notify(new[]
{
// removal must come first
- new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.RefreshBranch),
- new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch)
+ new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshBranch),
+ new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch)
}, out _, out _);
// changes that *I* make are immediately visible on the current snapshot
@@ -660,6 +691,96 @@ namespace Umbraco.Tests.PublishedContent
Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id);
}
+ [Test]
+ public void NestedVariationChildrenTest()
+ {
+ var mixedKits = GetNestedVariantKits();
+ Init(mixedKits);
+
+ var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
+ _snapshotAccessor.PublishedSnapshot = snapshot;
+
+ //TEST with en-us variation context
+
+ _variationAccesor.VariationContext = new VariationContext("en-US");
+
+ var documents = snapshot.Content.GetAtRoot().ToArray();
+ AssertDocuments(documents, "N1-en-US");
+
+ documents = snapshot.Content.GetById(1).Children().ToArray();
+ AssertDocuments(documents, "N4", "N7-en-US");
+
+ //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants
+ documents = snapshot.Content.GetById(4).Children().ToArray();
+ AssertDocuments(documents, "N10-en-US", "N11");
+
+ //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants
+ documents = snapshot.Content.GetById(7).Children().ToArray();
+ AssertDocuments(documents, "N12-en-US", "N13");
+
+ //TEST with fr-fr variation context
+
+ _variationAccesor.VariationContext = new VariationContext("fr-FR");
+
+ documents = snapshot.Content.GetAtRoot().ToArray();
+ AssertDocuments(documents, "N1-fr-FR");
+
+ documents = snapshot.Content.GetById(1).Children().ToArray();
+ AssertDocuments(documents, "N4", "N7-fr-FR");
+
+ //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants
+ documents = snapshot.Content.GetById(4).Children().ToArray();
+ AssertDocuments(documents, "N10-fr-FR", "N11");
+
+ //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants
+ documents = snapshot.Content.GetById(7).Children().ToArray();
+ AssertDocuments(documents, "N12-fr-FR", "N13");
+
+ //TEST specific cultures
+
+ documents = snapshot.Content.GetAtRoot("fr-FR").ToArray();
+ AssertDocuments(documents, "N1-fr-FR");
+
+ documents = snapshot.Content.GetById(1).Children("fr-FR").ToArray();
+ AssertDocuments(documents, "N4", "N7-fr-FR"); //NOTE: Returns invariant, this is expected
+ documents = snapshot.Content.GetById(1).Children("").ToArray();
+ AssertDocuments(documents, "N4"); //Only returns invariant since that is what was requested
+
+ documents = snapshot.Content.GetById(4).Children("fr-FR").ToArray();
+ AssertDocuments(documents, "N10-fr-FR", "N11"); //NOTE: Returns invariant, this is expected
+ documents = snapshot.Content.GetById(4).Children("").ToArray();
+ AssertDocuments(documents, "N11"); //Only returns invariant since that is what was requested
+
+ documents = snapshot.Content.GetById(7).Children("fr-FR").ToArray();
+ AssertDocuments(documents, "N12-fr-FR", "N13"); //NOTE: Returns invariant, this is expected
+ documents = snapshot.Content.GetById(7).Children("").ToArray();
+ AssertDocuments(documents, "N13"); //Only returns invariant since that is what was requested
+
+ //TEST without variation context
+ // This will actually convert the culture to "" which will be invariant since that's all it will know how to do
+ // This will return a NULL name for culture specific entities because there is no variation context
+
+ _variationAccesor.VariationContext = null;
+
+ documents = snapshot.Content.GetAtRoot().ToArray();
+ //will return nothing because there's only variant at root
+ Assert.AreEqual(0, documents.Length);
+ //so we'll continue to getting the known variant, do not fully assert this because the Name will NULL
+ documents = snapshot.Content.GetAtRoot("fr-FR").ToArray();
+ Assert.AreEqual(1, documents.Length);
+
+ documents = snapshot.Content.GetById(1).Children().ToArray();
+ AssertDocuments(documents, "N4");
+
+ //Get the invariant and list children
+ documents = snapshot.Content.GetById(4).Children().ToArray();
+ AssertDocuments(documents, "N11");
+
+ //Get the variant and list children
+ documents = snapshot.Content.GetById(7).Children().ToArray();
+ AssertDocuments(documents, "N13");
+ }
+
[Test]
public void VariantChildrenTest()
{
@@ -752,9 +873,9 @@ namespace Umbraco.Tests.PublishedContent
// notify
_snapshotService.Notify(new[]
{
- new ContentCacheRefresher.JsonPayload(3, TreeChangeTypes.Remove), // remove last
- new ContentCacheRefresher.JsonPayload(5, TreeChangeTypes.Remove), // remove middle
- new ContentCacheRefresher.JsonPayload(9, TreeChangeTypes.Remove), // remove first
+ new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove), // remove last
+ new ContentCacheRefresher.JsonPayload(5, Guid.Empty, TreeChangeTypes.Remove), // remove middle
+ new ContentCacheRefresher.JsonPayload(9, Guid.Empty, TreeChangeTypes.Remove), // remove first
}, out _, out _);
documents = snapshot.Content.GetAtRoot().ToArray();
@@ -769,9 +890,9 @@ namespace Umbraco.Tests.PublishedContent
// notify
_snapshotService.Notify(new[]
{
- new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.Remove), // remove first
- new ContentCacheRefresher.JsonPayload(8, TreeChangeTypes.Remove), // remove
- new ContentCacheRefresher.JsonPayload(7, TreeChangeTypes.Remove), // remove
+ new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.Remove), // remove first
+ new ContentCacheRefresher.JsonPayload(8, Guid.Empty, TreeChangeTypes.Remove), // remove
+ new ContentCacheRefresher.JsonPayload(7, Guid.Empty, TreeChangeTypes.Remove), // remove
}, out _, out _);
documents = snapshot.Content.GetAtRoot().ToArray();
@@ -801,8 +922,8 @@ namespace Umbraco.Tests.PublishedContent
// notify
_snapshotService.Notify(new[]
{
- new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch),
- new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.RefreshNode),
+ new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch),
+ new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshNode),
}, out _, out _);
documents = snapshot.Content.GetAtRoot().ToArray();
@@ -834,6 +955,198 @@ namespace Umbraco.Tests.PublishedContent
AssertDocuments(documents, "N1-en-US", "N2-en-US", "N3-en-US");
}
+ [Test]
+ public void Set_All_Fast_Sorted_Ensure_LastChildContentId()
+ {
+ //see https://github.com/umbraco/Umbraco-CMS/issues/6353
+
+ IEnumerable GetKits()
+ {
+ var paths = new Dictionary { { -1, "-1" } };
+
+ yield return CreateInvariantKit(1, -1, 1, paths);
+ yield return CreateInvariantKit(2, 1, 1, paths);
+ }
+
+ Init(GetKits());
+
+ var snapshotService = (PublishedSnapshotService)_snapshotService;
+ var contentStore = snapshotService.GetContentStore();
+
+ var parentNodes = contentStore.Test.GetValues(1);
+ var parentNode = parentNodes[0];
+ AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 2);
+
+ _snapshotService.Notify(new[]
+ {
+ new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.Remove)
+ }, out _, out _);
+
+ parentNodes = contentStore.Test.GetValues(1);
+ parentNode = parentNodes[0];
+
+ AssertLinkedNode(parentNode.contentNode, -1, -1, -1, -1, -1);
+ }
+
+ [Test]
+ public void Remove_Node_Ensures_Linked_List()
+ {
+ // NOTE: these tests are not using real scopes, in which case a Scope does not control
+ // how the snapshots generations work. We are forcing new snapshot generations manually.
+
+ IEnumerable GetKits()
+ {
+ var paths = new Dictionary { { -1, "-1" } };
+
+ //root
+ yield return CreateInvariantKit(1, -1, 1, paths);
+
+ //children
+ yield return CreateInvariantKit(2, 1, 1, paths);
+ yield return CreateInvariantKit(3, 1, 2, paths); //middle child
+ yield return CreateInvariantKit(4, 1, 3, paths);
+ }
+
+ Init(GetKits());
+
+ var snapshotService = (PublishedSnapshotService)_snapshotService;
+ var contentStore = snapshotService.GetContentStore();
+
+ Assert.AreEqual(1, contentStore.Test.LiveGen);
+ Assert.IsTrue(contentStore.Test.NextGen);
+
+ var parentNode = contentStore.Test.GetValues(1)[0];
+ Assert.AreEqual(1, parentNode.gen);
+ AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
+
+ var child1 = contentStore.Test.GetValues(2)[0];
+ Assert.AreEqual(1, child1.gen);
+ AssertLinkedNode(child1.contentNode, 1, -1, 3, -1, -1);
+
+ var child2 = contentStore.Test.GetValues(3)[0];
+ Assert.AreEqual(1, child2.gen);
+ AssertLinkedNode(child2.contentNode, 1, 2, 4, -1, -1);
+
+ var child3 = contentStore.Test.GetValues(4)[0];
+ Assert.AreEqual(1, child3.gen);
+ AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1);
+
+ //This will set a flag to force creating a new Gen next time the store is locked (i.e. In Notify)
+ contentStore.CreateSnapshot();
+
+ Assert.IsFalse(contentStore.Test.NextGen);
+
+ _snapshotService.Notify(new[]
+ {
+ new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove) //remove middle child
+ }, out _, out _);
+
+ Assert.AreEqual(2, contentStore.Test.LiveGen);
+ Assert.IsTrue(contentStore.Test.NextGen);
+
+ var parentNodes = contentStore.Test.GetValues(1);
+ Assert.AreEqual(1, parentNodes.Length); // the parent doesn't get changed, not new gen's are added
+ parentNode = parentNodes[0];
+ Assert.AreEqual(1, parentNode.gen); // the parent node's gen has not changed
+ AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
+
+ child1 = contentStore.Test.GetValues(2)[0];
+ Assert.AreEqual(2, child1.gen); // there is now 2x gen's of this item
+ AssertLinkedNode(child1.contentNode, 1, -1, 4, -1, -1);
+
+ child2 = contentStore.Test.GetValues(3)[0];
+ Assert.AreEqual(2, child2.gen); // there is now 2x gen's of this item
+ Assert.IsNull(child2.contentNode); // because it doesn't exist anymore
+
+ child3 = contentStore.Test.GetValues(4)[0];
+ Assert.AreEqual(2, child3.gen); // there is now 2x gen's of this item
+ AssertLinkedNode(child3.contentNode, 1, 2, -1, -1, -1);
+ }
+
+ [Test]
+ public void Refresh_Branch_Ensures_Linked_List()
+ {
+ // NOTE: these tests are not using real scopes, in which case a Scope does not control
+ // how the snapshots generations work. We are forcing new snapshot generations manually.
+
+ IEnumerable GetKits()
+ {
+ var paths = new Dictionary { { -1, "-1" } };
+
+ //root
+ yield return CreateInvariantKit(1, -1, 1, paths);
+
+ //children
+ yield return CreateInvariantKit(2, 1, 1, paths);
+ yield return CreateInvariantKit(3, 1, 2, paths); //middle child
+ yield return CreateInvariantKit(4, 1, 3, paths);
+ }
+
+ Init(GetKits());
+
+ var snapshotService = (PublishedSnapshotService)_snapshotService;
+ var contentStore = snapshotService.GetContentStore();
+
+ Assert.AreEqual(1, contentStore.Test.LiveGen);
+ Assert.IsTrue(contentStore.Test.NextGen);
+
+ var parentNode = contentStore.Test.GetValues(1)[0];
+ Assert.AreEqual(1, parentNode.gen);
+ AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
+
+ var child1 = contentStore.Test.GetValues(2)[0];
+ Assert.AreEqual(1, child1.gen);
+ AssertLinkedNode(child1.contentNode, 1, -1, 3, -1, -1);
+
+ var child2 = contentStore.Test.GetValues(3)[0];
+ Assert.AreEqual(1, child2.gen);
+ AssertLinkedNode(child2.contentNode, 1, 2, 4, -1, -1);
+
+ var child3 = contentStore.Test.GetValues(4)[0];
+ Assert.AreEqual(1, child3.gen);
+ AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1);
+
+ //This will set a flag to force creating a new Gen next time the store is locked (i.e. In Notify)
+ contentStore.CreateSnapshot();
+
+ Assert.IsFalse(contentStore.Test.NextGen);
+
+ _snapshotService.Notify(new[]
+ {
+ new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.RefreshBranch) //remove middle child
+ }, out _, out _);
+
+ Assert.AreEqual(2, contentStore.Test.LiveGen);
+ Assert.IsTrue(contentStore.Test.NextGen);
+
+ var parentNodes = contentStore.Test.GetValues(1);
+ Assert.AreEqual(1, parentNodes.Length); // the parent doesn't get changed, not new gen's are added
+ parentNode = parentNodes[0];
+ Assert.AreEqual(1, parentNode.gen); // the parent node's gen has not changed
+ AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
+
+ child1 = contentStore.Test.GetValues(2)[0];
+ Assert.AreEqual(2, child1.gen); // there is now 2x gen's of this item
+ AssertLinkedNode(child1.contentNode, 1, -1, 3, -1, -1);
+
+ child2 = contentStore.Test.GetValues(3)[0];
+ Assert.AreEqual(2, child2.gen); // there is now 2x gen's of this item
+ AssertLinkedNode(child2.contentNode, 1, 2, 4, -1, -1);
+
+ child3 = contentStore.Test.GetValues(4)[0];
+ Assert.AreEqual(2, child3.gen); // there is now 2x gen's of this item
+ AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1);
+ }
+
+ private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild)
+ {
+ Assert.AreEqual(parent, node.ParentContentId);
+ Assert.AreEqual(prevSibling, node.PreviousSiblingContentId);
+ Assert.AreEqual(nextSibling, node.NextSiblingContentId);
+ Assert.AreEqual(firstChild, node.FirstChildContentId);
+ Assert.AreEqual(lastChild, node.LastChildContentId);
+ }
+
private void AssertDocuments(IPublishedContent[] documents, params string[] names)
{
Assert.AreEqual(names.Length, documents.Length);
diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
index b66404c954..44df5c20cb 100644
--- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
+using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -128,12 +129,16 @@ namespace Umbraco.Tests.PublishedContent
_contentType
};
- var contentTypeService = Mock.Of();
- Mock.Get(contentTypeService).Setup(x => x.GetAll()).Returns(contentTypes);
- Mock.Get(contentTypeService).Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes);
+ var contentTypeService = new Mock();
+ contentTypeService.Setup(x => x.GetAll()).Returns(contentTypes);
+ 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 mediaTypeService = new Mock();
+ mediaTypeService.Setup(x => x.GetAll()).Returns(Enumerable.Empty());
+ mediaTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(Enumerable.Empty());
+
+ var contentTypeServiceBaseFactory = new Mock();
+ contentTypeServiceBaseFactory.Setup(x => x.For(It.IsAny())).Returns(contentTypeService.Object);
var dataTypeService = Mock.Of();
Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes);
@@ -143,8 +148,10 @@ namespace Umbraco.Tests.PublishedContent
dataTypeService: dataTypeService,
memberTypeService: Mock.Of(),
memberService: Mock.Of(),
- contentTypeService: contentTypeService,
- localizationService: Mock.Of()
+ contentTypeService: contentTypeService.Object,
+ mediaTypeService: mediaTypeService.Object,
+ localizationService: Mock.Of(),
+ domainService: Mock.Of()
);
// create a scope provider
@@ -169,7 +176,7 @@ namespace Umbraco.Tests.PublishedContent
_variationAccesor = new TestVariationContextAccessor();
// at last, create the complete NuCache snapshot service!
- var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
+ var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
_snapshotService = new PublishedSnapshotService(options,
null,
runtime,
@@ -178,7 +185,7 @@ namespace Umbraco.Tests.PublishedContent
null,
new TestPublishedSnapshotAccessor(),
_variationAccesor,
- Mock.Of(),
+ Mock.Of(),
scopeProvider,
Mock.Of(),
Mock.Of(),
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs
index 6907f6936c..f1e2bf20d6 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs
@@ -9,6 +9,8 @@ using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Web.PropertyEditors;
+using Umbraco.Core.Services;
+using Umbraco.Web;
namespace Umbraco.Tests.PublishedContent
{
@@ -38,7 +40,7 @@ namespace Umbraco.Tests.PublishedContent
var converters = Factory.GetInstance();
var dataTypeService = new TestObjects.TestDataTypeService(
- new DataType(new RichTextPropertyEditor(Mock.Of())) { Id = 1 });
+ new DataType(new RichTextPropertyEditor(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())) { Id = 1 });
var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService);
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
index f54971a197..6ef632bf90 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
@@ -42,10 +42,14 @@ namespace Umbraco.Tests.PublishedContent
Composition.RegisterUnique();
var logger = Mock.Of();
+ var mediaService = Mock.Of();
+ var contentTypeBaseServiceProvider = Mock.Of();
+ var umbracoContextAccessor = Mock.Of();
+
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new VoidEditor(logger)) { Id = 1 },
new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 },
- new DataType(new RichTextPropertyEditor(logger)) { Id = 1002 },
+ new DataType(new RichTextPropertyEditor(logger, mediaService, contentTypeBaseServiceProvider, umbracoContextAccessor)) { Id = 1002 },
new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 },
new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 },
new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 });
diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs
index 5af48e64b1..6489417dc7 100644
--- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs
+++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs
@@ -81,6 +81,19 @@ namespace Umbraco.Tests.Routing
Assert.AreEqual(expected, resolvedUrl);
}
+ [Test]
+ public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute()
+ {
+ const string expected = "http://localhost/media/rfeiw584/test.jpg";
+
+ var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider });
+ var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
+
+ var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Relative);
+
+ Assert.AreEqual(expected, resolvedUrl);
+ }
+
[Test]
public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported()
{
diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs
index 935417c510..135172460d 100644
--- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs
+++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing
{
base.SetUp();
- WebFinalComponent.CreateRoutes(
+ WebInitialComponent.CreateRoutes(
new TestUmbracoContextAccessor(),
TestObjects.GetGlobalSettings(),
new SurfaceControllerTypeCollection(Enumerable.Empty()),
diff --git a/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs
new file mode 100644
index 0000000000..4079ea6c84
--- /dev/null
+++ b/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs
@@ -0,0 +1,80 @@
+using System.Web.Mvc;
+using System.Web.Routing;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Configuration;
+using Umbraco.Tests.TestHelpers;
+using Umbraco.Web;
+
+namespace Umbraco.Tests.Routing
+{
+ [TestFixture]
+ public class RoutableDocumentFilterTests : BaseWebTest
+ {
+ [TestCase("/umbraco/editContent.aspx")]
+ [TestCase("/install/default.aspx")]
+ [TestCase("/install/")]
+ [TestCase("/install")]
+ [TestCase("/install/?installStep=asdf")]
+ [TestCase("/install/test.aspx")]
+ public void Is_Reserved_Path_Or_Url(string url)
+ {
+ var globalSettings = TestObjects.GetGlobalSettings();
+ var routableDocFilter = new RoutableDocumentFilter(globalSettings);
+ Assert.IsTrue(routableDocFilter.IsReservedPathOrUrl(url));
+ }
+
+ [TestCase("/base/somebasehandler")]
+ [TestCase("/")]
+ [TestCase("/home.aspx")]
+ [TestCase("/umbraco-test")]
+ [TestCase("/install-test")]
+ [TestCase("/install.aspx")]
+ public void Is_Not_Reserved_Path_Or_Url(string url)
+ {
+ var globalSettings = TestObjects.GetGlobalSettings();
+ var routableDocFilter = new RoutableDocumentFilter(globalSettings);
+ Assert.IsFalse(routableDocFilter.IsReservedPathOrUrl(url));
+ }
+
+ [TestCase("/Do/Not/match", false)]
+ [TestCase("/Umbraco/RenderMvcs", false)]
+ [TestCase("/Umbraco/RenderMvc", true)]
+ [TestCase("/Umbraco/RenderMvc/Index", true)]
+ [TestCase("/Umbraco/RenderMvc/Index/1234", true)]
+ [TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
+ [TestCase("/api", true)]
+ [TestCase("/api/WebApiTest", true)]
+ [TestCase("/api/WebApiTest/1234", true)]
+ [TestCase("/api/WebApiTest/Index/1234", false)]
+ public void Is_Reserved_By_Route(string url, bool shouldMatch)
+ {
+ //reset the app config, we only want to test routes not the hard coded paths
+ var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container
+ globalSettingsMock.Setup(x => x.ReservedPaths).Returns("");
+ globalSettingsMock.Setup(x => x.ReservedUrls).Returns("");
+
+ var routableDocFilter = new RoutableDocumentFilter(globalSettingsMock.Object);
+
+ var routes = new RouteCollection();
+
+ routes.MapRoute(
+ "Umbraco_default",
+ "Umbraco/RenderMvc/{action}/{id}",
+ new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional });
+ routes.MapRoute(
+ "WebAPI",
+ "api/{controller}/{id}",
+ new { controller = "WebApiTestController", action = "Index", id = UrlParameter.Optional });
+
+
+ var context = new FakeHttpContextFactory(url);
+
+
+ Assert.AreEqual(
+ shouldMatch,
+ routableDocFilter.IsReservedPathOrUrl(url, context.HttpContext, routes));
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
index 3e4c4f1ba9..e95fdfc87c 100644
--- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
+++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
@@ -38,15 +38,11 @@ namespace Umbraco.Tests.Routing
_module = new UmbracoInjectedModule
(
globalSettings,
- Mock.Of(),
- Factory.GetInstance(),
- Factory.GetInstance(),
- new UrlProviderCollection(new IUrlProvider[0]),
runtime,
logger,
null, // FIXME: PublishedRouter complexities...
- Mock.Of(),
- Mock.Of()
+ Mock.Of(),
+ new RoutableDocumentFilter(globalSettings)
);
runtime.Level = RuntimeLevel.Run;
diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
index 397a22fc62..c7c403b260 100644
--- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
+++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
@@ -72,16 +72,15 @@ namespace Umbraco.Tests.Scoping
protected override IPublishedSnapshotService CreatePublishedSnapshotService()
{
- var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
+ var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
var runtimeStateMock = new Mock();
runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run);
- var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), Mock.Of());
+ var contentTypeFactory = Factory.GetInstance();
var documentRepository = Mock.Of();
var mediaRepository = Mock.Of();
var memberRepository = Mock.Of();
- var contentTypeServiceBaseFactory = Current.Services.ContentTypeBaseServices;
return new PublishedSnapshotService(
options,
@@ -92,7 +91,7 @@ namespace Umbraco.Tests.Scoping
null,
publishedSnapshotAccessor,
Mock.Of(),
- Logger,
+ ProfilingLogger,
ScopeProvider,
documentRepository, mediaRepository, memberRepository,
DefaultCultureAccessor,
diff --git a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs
deleted file mode 100644
index c81c108e0d..0000000000
--- a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-using System.Collections.Specialized;
-using System.Web;
-using System.Web.Helpers;
-using Moq;
-using Newtonsoft.Json;
-using NUnit.Framework;
-using Umbraco.Core;
-using Umbraco.Tests.TestHelpers;
-using Umbraco.Web.Mvc;
-using Umbraco.Web.Security;
-
-namespace Umbraco.Tests.Security
-{
- [TestFixture]
- public class UmbracoAntiForgeryAdditionalDataProviderTests
- {
- [Test]
- public void Test_Wrapped_Non_BeginUmbracoForm()
- {
- var wrapped = Mock.Of(x => x.GetAdditionalData(It.IsAny()) == "custom");
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped);
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- var data = provider.GetAdditionalData(httpContextFactory.HttpContext);
-
- Assert.IsTrue(data.DetectIsJson());
- var json = JsonConvert.DeserializeObject(data);
- Assert.AreEqual(null, json.Ufprt);
- Assert.IsTrue(json.Stamp != default);
- Assert.AreEqual("custom", json.WrappedValue);
- }
-
- [Test]
- public void Null_Wrapped_Non_BeginUmbracoForm()
- {
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- var data = provider.GetAdditionalData(httpContextFactory.HttpContext);
-
- Assert.IsTrue(data.DetectIsJson());
- var json = JsonConvert.DeserializeObject(data);
- Assert.AreEqual(null, json.Ufprt);
- Assert.IsTrue(json.Stamp != default);
- Assert.AreEqual("default", json.WrappedValue);
- }
-
- [Test]
- public void Validate_Non_Json()
- {
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello");
-
- Assert.IsFalse(isValid);
- }
-
- [Test]
- public void Validate_Invalid_Json()
- {
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}");
- Assert.IsFalse(isValid);
-
- isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}");
- Assert.IsFalse(isValid);
-
- isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}");
- Assert.IsFalse(isValid);
-
- }
-
- [Test]
- public void Validate_No_Request_Ufprt()
- {
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- //there is a ufprt in the additional data, but not in the request
- var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}");
- Assert.IsFalse(isValid);
- }
-
- [Test]
- public void Validate_No_AdditionalData_Ufprt()
- {
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
- requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG");
-
- //there is a ufprt in the additional data, but not in the request
- var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}");
- Assert.IsFalse(isValid);
- }
-
- [Test]
- public void Validate_No_AdditionalData_Or_Request_Ufprt()
- {
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
-
- //there is a ufprt in the additional data, but not in the request
- var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}");
- Assert.IsTrue(isValid);
- }
-
- [Test]
- public void Validate_Request_And_AdditionalData_Ufprt()
- {
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
-
- var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
- var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
- requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey());
-
- var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
- Assert.IsTrue(isValid);
-
- routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
- isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
- Assert.IsFalse(isValid);
- }
-
- [Test]
- public void Validate_Wrapped_Request_And_AdditionalData_Ufprt()
- {
- var wrapped = Mock.Of(x => x.ValidateAdditionalData(It.IsAny(), "custom") == true);
- var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped);
-
- var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
- var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
-
- var httpContextFactory = new FakeHttpContextFactory("/hello/world");
- var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
- requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey());
-
- var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
- Assert.IsFalse(isValid);
-
- isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
- Assert.IsTrue(isValid);
-
- routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
- isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
- Assert.IsFalse(isValid);
- }
- }
-}
diff --git a/src/Umbraco.Tests/Services/AuditServiceTests.cs b/src/Umbraco.Tests/Services/AuditServiceTests.cs
index 6064fe4acc..bfec246e61 100644
--- a/src/Umbraco.Tests/Services/AuditServiceTests.cs
+++ b/src/Umbraco.Tests/Services/AuditServiceTests.cs
@@ -4,6 +4,7 @@ using NUnit.Framework;
using Umbraco.Core.Services.Implement;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
+using Umbraco.Core.Models;
namespace Umbraco.Tests.Services
{
@@ -48,5 +49,19 @@ namespace Umbraco.Tests.Services
Assert.AreEqual(123 + 5, entries[0].PerformingUserId);
Assert.AreEqual(123 + 4, entries[1].PerformingUserId);
}
+
+ [Test]
+ public void CanReadEntries()
+ {
+ var yesterday = DateTime.UtcNow.AddDays(-1);
+
+ for (var i = 0; i < 10; i++)
+ {
+ yesterday = yesterday.AddMinutes(1);
+ ServiceContext.AuditService.Add(AuditType.Unpublish, -1, 33, "", "blah");
+ }
+
+ var logs = ServiceContext.AuditService.GetUserLogs(-1, AuditType.Unpublish);
+ }
}
}
diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs
index ed5e6073ac..ef80672baf 100644
--- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs
+++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs
@@ -166,8 +166,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -200,8 +200,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -232,8 +232,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
- var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -267,8 +267,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
- var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs
index 222f40aeed..7cfc650510 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -717,21 +717,8 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Unpublish_Content_Variation()
{
- // Arrange
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
- var langUk = new Language("en-GB") { IsDefault = true };
- var langFr = new Language("fr-FR");
-
- ServiceContext.LocalizationService.Save(langFr);
- ServiceContext.LocalizationService.Save(langUk);
-
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
-
- IContent content = new Content("content", Constants.System.Root, contentType);
- content.SetCultureName("content-fr", langFr.IsoCode);
- content.SetCultureName("content-en", langUk.IsoCode);
content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault));
content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
@@ -761,6 +748,185 @@ namespace Umbraco.Tests.Services
}
+ [Test]
+ public void Can_Publish_Culture_After_Last_Culture_Unpublished()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //first culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //last culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id); //reget
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+
+ }
+
+
+
+ [Test]
+ public void Unpublish_All_Cultures_Has_Unpublished_State()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //first culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.AreEqual(PublishedState.Published, content.PublishedState); //still published
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+
+ unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //last culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //the last culture was unpublished so the document should also reflect this
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ }
+
+ [Test]
+ public void Unpublishing_Mandatory_Language_Unpublishes_Document()
+ {
+ var langUk = new Language("en-GB") { IsDefault = true, IsMandatory = true };
+ var langFr = new Language("fr-FR");
+
+ ServiceContext.LocalizationService.Save(langFr);
+ ServiceContext.LocalizationService.Save(langUk);
+
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ IContent content = new Content("content", Constants.System.Root, contentType);
+ content.SetCultureName("content-fr", langFr.IsoCode);
+ content.SetCultureName("content-en", langUk.IsoCode);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish mandatory lang
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishMandatoryCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); //remains published
+ Assert.AreEqual(PublishedState.Unpublished, content.PublishedState);
+ }
+
+ [Test]
+ public void Unpublishing_Already_Unpublished_Culture()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode);
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ //Change some data since Unpublish should always Save
+ content.SetCultureName("content-en-updated", langUk.IsoCode);
+
+ unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishAlready, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+ //ensure that even though the culture was already unpublished that the data was still persisted
+ Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
+ }
+
+ [Test]
+ public void Publishing_No_Cultures_Still_Saves()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ //Change some data since SaveAndPublish should always Save
+ content.SetCultureName("content-en-updated", langUk.IsoCode);
+
+ var saved = ServiceContext.ContentService.SaveAndPublish(content, new string [] { }); //save without cultures
+ Assert.AreEqual(PublishResultType.FailedPublishNothingToPublish, saved.Result);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ //ensure that even though nothing was published that the data was still persisted
+ Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
+ }
+
+
[Test]
public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State()
{
@@ -811,17 +977,7 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures()
{
- // Arrange
-
- var langGB = new Language("en-GB") { IsDefault = true };
- var langFr = new Language("fr-FR");
-
- ServiceContext.LocalizationService.Save(langFr);
- ServiceContext.LocalizationService.Save(langGB);
-
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
+ CreateEnglishAndFrenchDocumentType(out var langUk, out var langFr, out var contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
@@ -832,8 +988,8 @@ namespace Umbraco.Tests.Services
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
- content.SetCultureName("content-en", langGB.IsoCode);
- published = ServiceContext.ContentService.SaveAndPublish(content, langGB.IsoCode);
+ content.SetCultureName("content-en", langUk.IsoCode);
+ published = ServiceContext.ContentService.SaveAndPublish(content, langUk.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);
@@ -3008,10 +3164,33 @@ namespace Umbraco.Tests.Services
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
+
+ private void CreateEnglishAndFrenchDocumentType(out Language langUk, out Language langFr, out ContentType contentType)
+ {
+ langUk = new Language("en-GB") { IsDefault = true };
+ langFr = new Language("fr-FR");
+ ServiceContext.LocalizationService.Save(langFr);
+ ServiceContext.LocalizationService.Save(langUk);
+
+ contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ }
+
+ private IContent CreateEnglishAndFrenchDocument(out Language langUk, out Language langFr, out ContentType contentType)
+ {
+ CreateEnglishAndFrenchDocumentType(out langUk, out langFr, out contentType);
+
+ IContent content = new Content("content", Constants.System.Root, contentType);
+ content.SetCultureName("content-fr", langFr.IsoCode);
+ content.SetCultureName("content-en", langUk.IsoCode);
+
+ return content;
+ }
}
}
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
index a0b5f01a1f..f2a4368ae4 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
@@ -53,360 +53,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(contentType.IsElement);
}
- [Test]
- public void Change_Content_Type_Variation_Clears_Redirects()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
- var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- ServiceContext.ContentTypeService.Save(contentType2);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.Name = "Hello1";
- ServiceContext.ContentService.Save(doc);
-
- IContent doc2 = MockedContent.CreateBasicContent(contentType2);
- ServiceContext.ContentService.Save(doc2);
-
- ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
- ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
-
- Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
- Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
-
- //change variation
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
-
- Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
- Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
-
- }
-
- [Test]
- public void Change_Content_Type_From_Invariant_Variant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.Name = "Hello1";
- doc.SetValue("title", "hello world");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("Hello1", doc.Name);
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change the content type to be variant, we will also update the name here to detect the copy changes
- doc.Name = "Hello2";
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
- Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
-
- //change back property type to be invariant, we will also update the name here to detect the copy changes
- doc.SetCultureName("Hello3", "en-US");
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello3", doc.Name);
- Assert.AreEqual("hello world", doc.GetValue("title"));
- }
-
- [Test]
- public void Change_Content_Type_From_Variant_Invariant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Hello1", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- //change the content type to be invariant, we will also update the name here to detect the copy changes
- doc.SetCultureName("Hello2", "en-US");
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello2", doc.Name);
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change back property type to be variant, we will also update the name here to detect the copy changes
- doc.Name = "Hello3";
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- //at this stage all property types were switched to invariant so even though the variant value
- //exists it will not be returned because the property type is invariant,
- //so this check proves that null will be returned
- Assert.IsNull(doc.GetValue("title", "en-US"));
-
- //we can now switch the property type to be variant and the value can be returned again
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- }
-
- [Test]
- public void Change_Property_Type_From_Invariant_Variant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.Name = "Home";
- doc.SetValue("title", "hello world");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change the property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- //change back property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
- }
-
- [Test]
- public void Change_Property_Type_From_Variant_Invariant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Home", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- //change the property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change back property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
- }
-
- [Test]
- public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //compose this from the other one
- var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- contentType2.Variations = ContentVariation.Culture;
- contentType2.AddContentType(contentType);
- ServiceContext.ContentTypeService.Save(contentType2);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Home", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- IContent doc2 = MockedContent.CreateBasicContent(contentType2);
- doc2.SetCultureName("Home", "en-US");
- doc2.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc2);
-
- //change the property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
- Assert.AreEqual("hello world", doc2.GetValue("title"));
-
- //change back property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
- Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
- }
-
- [Test]
- public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //compose this from the other one
- var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- contentType2.Variations = ContentVariation.Culture;
- contentType2.AddContentType(contentType);
- ServiceContext.ContentTypeService.Save(contentType2);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Home", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- IContent doc2 = MockedContent.CreateBasicContent(contentType2);
- doc2.SetCultureName("Home", "en-US");
- doc2.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc2);
-
- //change the content type to be invariant
- contentType.Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
- Assert.AreEqual("hello world", doc2.GetValue("title"));
-
- //change back content type to be variant
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- //this will be null because the doc type was changed back to variant but it's property types don't get changed back
- Assert.IsNull(doc.GetValue("title", "en-US"));
- Assert.IsNull(doc2.GetValue("title", "en-US"));
- }
+
[Test]
public void Deleting_Content_Type_With_Hierarchy_Of_Content_Items_Moves_Orphaned_Content_To_Recycle_Bin()
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
index 3121988bfe..d618bcf1ad 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
@@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
+using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
@@ -42,17 +43,15 @@ namespace Umbraco.Tests.Services
protected override IPublishedSnapshotService CreatePublishedSnapshotService()
{
- var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
+ var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
var runtimeStateMock = new Mock();
runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run);
- var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), Mock.Of());
- //var documentRepository = Mock.Of();
+ var contentTypeFactory = Factory.GetInstance();
var documentRepository = Factory.GetInstance();
var mediaRepository = Mock.Of();
var memberRepository = Mock.Of();
- var contentTypeServiceBaseFactory = Current.Services.ContentTypeBaseServices;
return new PublishedSnapshotService(
options,
@@ -63,7 +62,7 @@ namespace Umbraco.Tests.Services
null,
publishedSnapshotAccessor,
Mock.Of(),
- Logger,
+ ProfilingLogger,
ScopeProvider,
documentRepository, mediaRepository, memberRepository,
DefaultCultureAccessor,
@@ -106,44 +105,346 @@ namespace Umbraco.Tests.Services
}
}
+ [Test]
+ public void Change_Content_Type_Variation_Clears_Redirects()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Nothing;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+ var contentType2 = MockedContentTypes.CreateBasicContentType("test");
+ ServiceContext.ContentTypeService.Save(contentType2);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.Name = "Hello1";
+ ServiceContext.ContentService.Save(doc);
+
+ IContent doc2 = MockedContent.CreateBasicContent(contentType2);
+ ServiceContext.ContentService.Save(doc2);
+
+ ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
+ ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
+
+ Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
+ Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
+
+ //change variation
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
+ Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
+
+ }
+
+ [Test]
+ public void Change_Content_Type_From_Invariant_Variant()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Nothing;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.Name = "Hello1";
+ doc.SetValue("title", "hello world");
+ ServiceContext.ContentService.Save(doc);
+
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello1", doc.Name);
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsFalse (doc.IsCultureEdited("en-US"));
+
+ //change the content type to be variant, we will also update the name here to detect the copy changes
+ doc.Name = "Hello2";
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
+ Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+
+ //change back property type to be invariant, we will also update the name here to detect the copy changes
+ doc.SetCultureName("Hello3", "en-US");
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello3", doc.Name);
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsFalse(doc.IsCultureEdited("en-US"));
+ }
+
+ [Test]
+ public void Change_Content_Type_From_Variant_Invariant()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Hello1", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+
+ //change the content type to be invariant, we will also update the name here to detect the copy changes
+ doc.SetCultureName("Hello2", "en-US");
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello2", doc.Name);
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsFalse(doc.IsCultureEdited("en-US"));
+
+ //change back property type to be variant, we will also update the name here to detect the copy changes
+ doc.Name = "Hello3";
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ //at this stage all property types were switched to invariant so even though the variant value
+ //exists it will not be returned because the property type is invariant,
+ //so this check proves that null will be returned
+ Assert.IsNull(doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language
+
+ //we can now switch the property type to be variant and the value can be returned again
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+
+ }
+
+
+ [Test]
+ public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Nothing;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //change the property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+
+ //Cannot change a property type to be variant if the content type itself is not variant
+ Assert.Throws(() => ServiceContext.ContentTypeService.Save(contentType));
+ }
+
+ [Test]
+ public void Change_Property_Type_From_Invariant_Variant()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world");
+ ServiceContext.ContentService.Save(doc);
+
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
+ Assert.IsTrue(doc.Edited);
+
+ //change the property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+ Assert.IsTrue(doc.Edited);
+
+ //change back property type to be invariant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
+ Assert.IsTrue(doc.Edited);
+ }
+
+ [Test]
+ public void Change_Property_Type_From_Variant_Invariant()
+ {
+ //create content type with a property type that varies by culture
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+
+ //change the property type to be invariant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+
+ //change back property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ }
+
+ [Test]
+ public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
+ {
+ //create content type with a property type that varies by culture
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //compose this from the other one
+ var contentType2 = MockedContentTypes.CreateBasicContentType("test");
+ contentType2.Variations = ContentVariation.Culture;
+ contentType2.AddContentType(contentType);
+ ServiceContext.ContentTypeService.Save(contentType2);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ IContent doc2 = MockedContent.CreateBasicContent(contentType2);
+ doc2.SetCultureName("Home", "en-US");
+ doc2.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc2);
+
+ //change the property type to be invariant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.AreEqual("hello world", doc2.GetValue("title"));
+
+ //change back property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
+ }
+
+ [Test]
+ public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
+ {
+ //create content type with a property type that varies by culture
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //compose this from the other one
+ var contentType2 = MockedContentTypes.CreateBasicContentType("test");
+ contentType2.Variations = ContentVariation.Culture;
+ contentType2.AddContentType(contentType);
+ ServiceContext.ContentTypeService.Save(contentType2);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ IContent doc2 = MockedContent.CreateBasicContent(contentType2);
+ doc2.SetCultureName("Home", "en-US");
+ doc2.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc2);
+
+ //change the content type to be invariant
+ contentType.Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.AreEqual("hello world", doc2.GetValue("title"));
+
+ //change back content type to be variant
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ //this will be null because the doc type was changed back to variant but it's property types don't get changed back
+ Assert.IsNull(doc.GetValue("title", "en-US"));
+ Assert.IsNull(doc2.GetValue("title", "en-US"));
+ }
+
[Test]
public void Change_Variations_SimpleContentType_VariantToInvariantAndBack()
{
// one simple content type, variant, with both variant and invariant properties
// can change it to invariant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var contentType = new ContentType(-1)
- {
- Alias = "contentType",
- Name = "contentType",
- Variations = ContentVariation.Culture
- };
+ var contentType = CreateContentType(ContentVariation.Culture);
- var properties = new PropertyTypeCollection(true)
- {
- new PropertyType("value1", ValueStorageType.Ntext)
- {
- Alias = "value1",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value2", ValueStorageType.Ntext)
- {
- Alias = "value2",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties = CreatePropertyCollection(
+ ("value1", ContentVariation.Culture),
+ ("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
- var document = (IContent) new Content("document", -1, contentType);
+ var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en", "en");
@@ -226,28 +527,11 @@ namespace Umbraco.Tests.Services
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
- var contentType = new ContentType(-1)
- {
- Alias = "contentType",
- Name = "contentType",
- Variations = ContentVariation.Nothing
- };
+ var contentType = CreateContentType(ContentVariation.Nothing);
- var properties = new PropertyTypeCollection(true)
- {
- new PropertyType("value1", ValueStorageType.Ntext)
- {
- Alias = "value1",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- },
- new PropertyType("value2", ValueStorageType.Ntext)
- {
- Alias = "value2",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties = CreatePropertyCollection(
+ ("value1", ContentVariation.Nothing),
+ ("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -326,33 +610,13 @@ namespace Umbraco.Tests.Services
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var contentType = new ContentType(-1)
- {
- Alias = "contentType",
- Name = "contentType",
- Variations = ContentVariation.Culture
- };
+ var contentType = CreateContentType(ContentVariation.Culture);
- var properties = new PropertyTypeCollection(true)
- {
- new PropertyType("value1", ValueStorageType.Ntext)
- {
- Alias = "value1",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value2", ValueStorageType.Ntext)
- {
- Alias = "value2",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties = CreatePropertyCollection(
+ ("value1", ContentVariation.Culture),
+ ("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -429,6 +693,185 @@ namespace Umbraco.Tests.Services
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
}
+ [Test]
+ public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized()
+ {
+ // one simple content type, variant, with both variant and invariant properties
+ // can change an invariant property to variant and back
+
+ CreateFrenchAndEnglishLangs();
+
+ var contentType = CreateContentType(ContentVariation.Culture);
+
+ var properties = CreatePropertyCollection(("value1", ContentVariation.Culture));
+
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ var document = (IContent)new Content("document", -1, contentType);
+ document.SetCultureName("doc1en", "en");
+ document.SetCultureName("doc1fr", "fr");
+ document.SetValue("value1", "v1en-init", "en");
+ document.SetValue("value1", "v1fr-init", "fr");
+ ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsFalse(document.IsCultureEdited("en"));
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsFalse(document.Edited);
+
+ document.SetValue("value1", "v1en", "en"); //change the property culture value, so now this culture will be edited
+ document.SetValue("value1", "v1fr", "fr"); //change the property culture value, so now this culture will be edited
+ ServiceContext.ContentService.Save(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.AreEqual("v1en", document.GetValue("value1", "en"));
+ Assert.AreEqual("v1en-init", document.GetValue("value1", "en", published: true));
+ Assert.AreEqual("v1fr", document.GetValue("value1", "fr"));
+ Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true));
+ Assert.IsTrue(document.IsCultureEdited("en")); //This will be true because the edited value isn't the same as the published value
+ Assert.IsTrue(document.IsCultureEdited("fr")); //This will be true because the edited value isn't the same as the published value
+ Assert.IsTrue(document.Edited);
+
+ // switch property type to Invariant
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsTrue(document.IsCultureEdited("en")); //This will remain true because there is now a pending change for the invariant property data which is flagged under the default lang
+ Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
+ Assert.IsTrue(document.Edited);
+
+ //update the invariant value and publish
+ document.SetValue("value1", "v1inv");
+ ServiceContext.ContentService.SaveAndPublish(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "en", published: true)); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
+ Assert.AreEqual("v1inv", document.GetValue("value1"));
+ Assert.AreEqual("v1inv", document.GetValue("value1", published: true));
+ Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
+ Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
+ Assert.IsFalse(document.Edited);
+
+ // switch property back to Culture
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("v1inv", document.GetValue("value1", "en")); //The invariant property value gets copied over to the default language
+ Assert.AreEqual("v1inv", document.GetValue("value1", "en", published: true));
+ Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); //values are still retained
+ Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true)); //values are still retained
+ Assert.IsFalse(document.IsCultureEdited("en")); //The invariant published AND edited values are copied over to the default language
+ Assert.IsTrue(document.IsCultureEdited("fr")); //The previously existing french values are there and there is no published value
+ Assert.IsTrue(document.Edited); //Will be flagged edited again because the french culture had pending changes
+
+ // publish again
+ document.SetValue("value1", "v1en2", "en"); //update the value now that it's variant again
+ document.SetValue("value1", "v1fr2", "fr"); //update the value now that it's variant again
+ ServiceContext.ContentService.SaveAndPublish(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
+ Assert.AreEqual("v1fr2", document.GetValue("value1", "fr"));
+ Assert.IsNull(document.GetValue("value1")); //The value is there but the business logic returns null
+ Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the variant property value has been published
+ Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the variant property value has been published
+ Assert.IsFalse(document.Edited);
+ }
+
+ [Test]
+ public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized()
+ {
+ // one simple content type, variant, with both variant and invariant properties
+ // can change an invariant property to variant and back
+
+ CreateFrenchAndEnglishLangs();
+
+ var contentType = CreateContentType(ContentVariation.Culture);
+
+ var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing));
+
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ var document = (IContent)new Content("document", -1, contentType);
+ document.SetCultureName("doc1en", "en");
+ document.SetCultureName("doc1fr", "fr");
+ document.SetValue("value1", "v1en-init");
+ ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsFalse(document.IsCultureEdited("en"));
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsFalse(document.Edited);
+
+ document.SetValue("value1", "v1en"); //change the property value, so now the invariant (default) culture will be edited
+ ServiceContext.ContentService.Save(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.AreEqual("v1en", document.GetValue("value1"));
+ Assert.AreEqual("v1en-init", document.GetValue("value1", published: true));
+ Assert.IsTrue(document.IsCultureEdited("en")); //This is true because the invariant property reflects changes on the default lang
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsTrue(document.Edited);
+
+ // switch property type to Culture
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsTrue(document.IsCultureEdited("en")); //Remains true
+ Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
+ Assert.IsTrue(document.Edited);
+
+ //update the culture value and publish
+ document.SetValue("value1", "v1en2", "en");
+ ServiceContext.ContentService.SaveAndPublish(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.IsNull(document.GetValue("value1")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", published: true)); //The values are there but the business logic returns null
+ Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
+ Assert.AreEqual("v1en2", document.GetValue("value1", "en", published: true));
+ Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
+ Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
+ Assert.IsFalse(document.Edited);
+
+ // switch property back to Invariant
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("v1en2", document.GetValue("value1")); //The variant property value gets copied over to the invariant
+ Assert.AreEqual("v1en2", document.GetValue("value1", published: true));
+ Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
+ Assert.IsFalse(document.IsCultureEdited("en")); //The variant published AND edited values are copied over to the invariant
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsFalse(document.Edited);
+
+ }
+
[Test]
public void Change_Variations_ComposedContentType_1()
{
@@ -437,59 +880,22 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the composed content type to invariant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var composing = new ContentType(-1)
- {
- Alias = "composing",
- Name = "composing",
- Variations = ContentVariation.Culture
- };
+ var composing = CreateContentType(ContentVariation.Culture, "composing");
- var properties1 = new PropertyTypeCollection(true)
- {
- new PropertyType("value11", ValueStorageType.Ntext)
- {
- Alias = "value11",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value12", ValueStorageType.Ntext)
- {
- Alias = "value12",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties1 = CreatePropertyCollection(
+ ("value11", ContentVariation.Culture),
+ ("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
- var composed = new ContentType(-1)
- {
- Alias = "composed",
- Name = "composed",
- Variations = ContentVariation.Culture
- };
+ var composed = CreateContentType(ContentVariation.Culture, "composed");
- var properties2 = new PropertyTypeCollection(true)
- {
- new PropertyType("value21", ValueStorageType.Ntext)
- {
- Alias = "value21",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value22", ValueStorageType.Ntext)
- {
- Alias = "value22",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties2 = CreatePropertyCollection(
+ ("value21", ContentVariation.Culture),
+ ("value22", ContentVariation.Nothing));
composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed.AddContentType(composing);
@@ -569,86 +975,32 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the variant composed content type to invariant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var composing = new ContentType(-1)
- {
- Alias = "composing",
- Name = "composing",
- Variations = ContentVariation.Culture
- };
+ var composing = CreateContentType(ContentVariation.Culture, "composing");
- var properties1 = new PropertyTypeCollection(true)
- {
- new PropertyType("value11", ValueStorageType.Ntext)
- {
- Alias = "value11",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value12", ValueStorageType.Ntext)
- {
- Alias = "value12",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties1 = CreatePropertyCollection(
+ ("value11", ContentVariation.Culture),
+ ("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
- var composed1 = new ContentType(-1)
- {
- Alias = "composed1",
- Name = "composed1",
- Variations = ContentVariation.Culture
- };
+ var composed1 = CreateContentType(ContentVariation.Culture, "composed1");
- var properties2 = new PropertyTypeCollection(true)
- {
- new PropertyType("value21", ValueStorageType.Ntext)
- {
- Alias = "value21",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value22", ValueStorageType.Ntext)
- {
- Alias = "value22",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties2 = CreatePropertyCollection(
+ ("value21", ContentVariation.Culture),
+ ("value22", ContentVariation.Nothing));
composed1.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed1.AddContentType(composing);
ServiceContext.ContentTypeService.Save(composed1);
- var composed2 = new ContentType(-1)
- {
- Alias = "composed2",
- Name = "composed2",
- Variations = ContentVariation.Nothing
- };
+ var composed2 = CreateContentType(ContentVariation.Nothing, "composed2");
- var properties3 = new PropertyTypeCollection(true)
- {
- new PropertyType("value31", ValueStorageType.Ntext)
- {
- Alias = "value31",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- },
- new PropertyType("value32", ValueStorageType.Ntext)
- {
- Alias = "value32",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties3 = CreatePropertyCollection(
+ ("value31", ContentVariation.Nothing),
+ ("value32", ContentVariation.Nothing));
composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" });
composed2.AddContentType(composing);
@@ -754,5 +1106,35 @@ namespace Umbraco.Tests.Services
AssertJsonStartsWith(document2.Id,
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
}
+
+ private void CreateFrenchAndEnglishLangs()
+ {
+ var languageEn = new Language("en") { IsDefault = true };
+ ServiceContext.LocalizationService.Save(languageEn);
+ var languageFr = new Language("fr");
+ ServiceContext.LocalizationService.Save(languageFr);
+ }
+
+ private IContentType CreateContentType(ContentVariation variance, string alias = "contentType") => new ContentType(-1)
+ {
+ Alias = alias,
+ Name = alias,
+ Variations = variance
+ };
+
+ private PropertyTypeCollection CreatePropertyCollection(params (string alias, ContentVariation variance)[] props)
+ {
+ var propertyCollection = new PropertyTypeCollection(true);
+
+ foreach (var (alias, variance) in props)
+ propertyCollection.Add(new PropertyType(alias, ValueStorageType.Ntext)
+ {
+ Alias = alias,
+ DataTypeId = -88,
+ Variations = variance
+ });
+
+ return propertyCollection;
+ }
}
}
diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs
index cb55a891a7..340ada2519 100644
--- a/src/Umbraco.Tests/Services/MemberServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs
@@ -11,15 +11,20 @@ using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
+using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.PropertyEditors;
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.Testing.Objects.Accessors;
+using Umbraco.Web;
+using Umbraco.Web.PublishedCache.NuCache;
using Umbraco.Web.Security.Providers;
namespace Umbraco.Tests.Services
@@ -43,6 +48,61 @@ namespace Umbraco.Tests.Services
((MemberService)ServiceContext.MemberService).MembershipProvider = provider;
}
+ [Test]
+ public void Can_Create_Member_With_Properties()
+ {
+ var memberType = ServiceContext.MemberTypeService.Get("member");
+ IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true);
+ ServiceContext.MemberService.Save(member);
+
+ member = ServiceContext.MemberService.GetById(member.Id);
+ Assert.AreEqual("xemail", member.Email);
+
+ var dataTypeService = ServiceContext.DataTypeService;
+ var contentTypeFactory = new PublishedContentTypeFactory(new NoopPublishedModelFactory(), new PropertyValueConverterCollection(Enumerable.Empty()), dataTypeService);
+ var pmemberType = new PublishedContentType(memberType, contentTypeFactory);
+
+ var publishedSnapshotAccessor = new TestPublishedSnapshotAccessor();
+ var variationContextAccessor = new TestVariationContextAccessor();
+ var pmember = PublishedMember.Create(member, pmemberType, false, publishedSnapshotAccessor, variationContextAccessor);
+
+ // contains the umbracoMember... properties created when installing, on the member type
+ // contains the other properties, that PublishedContentType adds (BuiltinMemberProperties)
+ //
+ // TODO: see TODO in PublishedContentType, this list contains duplicates
+
+ var aliases = new[]
+ {
+ "umbracoMemberPasswordRetrievalQuestion",
+ "umbracoMemberPasswordRetrievalAnswer",
+ "umbracoMemberComments",
+ "umbracoMemberFailedPasswordAttempts",
+ "umbracoMemberApproved",
+ "umbracoMemberLockedOut",
+ "umbracoMemberLastLockoutDate",
+ "umbracoMemberLastLogin",
+ "umbracoMemberLastPasswordChangeDate",
+ "Email",
+ "Username",
+ "PasswordQuestion",
+ "Comments",
+ "IsApproved",
+ "IsLockedOut",
+ "LastLockoutDate",
+ "CreateDate",
+ "LastLoginDate",
+ "LastPasswordChangeDate"
+ };
+
+ var properties = pmember.Properties.ToList();
+
+ for (var i = 0; i < aliases.Length; i++)
+ Assert.AreEqual(properties[i].Alias, aliases[i]);
+
+ var email = properties[aliases.IndexOf("Email")];
+ Assert.AreEqual("xemail", email.GetSourceValue());
+ }
+
[Test]
public void Can_Set_Password_On_New_Member()
{
diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs
index 8c598281dd..9f9f933d72 100644
--- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs
+++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs
@@ -25,19 +25,23 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
public async Task> Execute(string controllerName, string actionName, HttpMethod method,
HttpContent content = null,
MediaTypeWithQualityHeaderValue mediaTypeHeader = null,
- bool assertOkResponse = true)
+ bool assertOkResponse = true, object routeDefaults = null, string url = null)
{
if (mediaTypeHeader == null)
{
mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json");
}
+ if (routeDefaults == null)
+ {
+ routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional };
+ }
var startup = new TestStartup(
configuration =>
{
configuration.Routes.MapHttpRoute("Default",
routeTemplate: "{controller}/{action}/{id}",
- defaults: new { controller = controllerName, action = actionName, id = RouteParameter.Optional });
+ defaults: routeDefaults);
},
_controllerFactory);
@@ -45,7 +49,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
{
var request = new HttpRequestMessage
{
- RequestUri = new Uri("https://testserver/"),
+ RequestUri = new Uri("https://testserver/" + (url ?? "")),
Method = method
};
diff --git a/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs
new file mode 100644
index 0000000000..34904db1ae
--- /dev/null
+++ b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs
@@ -0,0 +1,22 @@
+using Lucene.Net.Store;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Umbraco.Tests.TestHelpers
+{
+
+ ///
+ /// Used for tests with Lucene so that each RAM directory is unique
+ ///
+ public class RandomIdRAMDirectory : RAMDirectory
+ {
+ private readonly string _lockId = Guid.NewGuid().ToString();
+ public override string GetLockId()
+ {
+ return _lockId;
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs
index 31d11952ef..56ad22d414 100644
--- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs
@@ -178,7 +178,7 @@ namespace Umbraco.Tests.TestHelpers
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, "installedPackages.config"),
new PackageInstallation(
- new PackageDataInstallation(logger, fileService.Value, macroService.Value, localizationService.Value, dataTypeService.Value, entityService.Value, contentTypeService.Value, contentService.Value, propertyEditorCollection),
+ new PackageDataInstallation(logger, fileService.Value, macroService.Value, localizationService.Value, dataTypeService.Value, entityService.Value, contentTypeService.Value, contentService.Value, propertyEditorCollection, scopeProvider),
new PackageFileInstallation(compiledPackageXmlParser, new ProfilingLogger(logger, new TestProfiler())),
compiledPackageXmlParser, Mock.Of(),
new DirectoryInfo(IOHelper.GetRootDirectorySafe())));
diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
index 146cb23c1f..9cc2b97445 100644
--- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
@@ -31,6 +31,7 @@ using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.Testing.Objects.Accessors;
+using Umbraco.Web.Cache;
namespace Umbraco.Tests.TestHelpers
{
diff --git a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs
index 72fb89ab82..0291715e46 100644
--- a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs
+++ b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs
@@ -29,37 +29,44 @@ namespace Umbraco.Tests.Testing.Objects
public IEnumerable GetAllContentSources(IScope scope)
=> Kits.Values
.OrderBy(x => x.Node.Level)
+ .ThenBy(x => x.Node.ParentContentId)
+ .ThenBy(x => x.Node.SortOrder)
.Select(x => x.Clone());
public IEnumerable GetBranchContentSources(IScope scope, int id)
=> Kits.Values
.Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ","))
- .OrderBy(x => x.Node.Level).ThenBy(x => x.Node.SortOrder)
+ .OrderBy(x => x.Node.Level)
+ .ThenBy(x => x.Node.ParentContentId)
+ .ThenBy(x => x.Node.SortOrder)
.Select(x => x.Clone());
public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids)
=> Kits.Values
.Where(x => ids.Contains(x.ContentTypeId))
+ .OrderBy(x => x.Node.Level)
+ .ThenBy(x => x.Node.ParentContentId)
+ .ThenBy(x => x.Node.SortOrder)
.Select(x => x.Clone());
public ContentNodeKit GetMediaSource(IScope scope, int id)
{
- throw new NotImplementedException();
+ return default;
}
public IEnumerable GetAllMediaSources(IScope scope)
{
- throw new NotImplementedException();
+ return Enumerable.Empty();
}
public IEnumerable GetBranchMediaSources(IScope scope, int id)
{
- throw new NotImplementedException();
+ return Enumerable.Empty();
}
public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids)
{
- throw new NotImplementedException();
+ return Enumerable.Empty();
}
}
}
diff --git a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs b/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs
index db7dd25152..9f72a022f3 100644
--- a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs
+++ b/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Reflection;
using NUnit.Framework;
+using Umbraco.Core.Exceptions;
namespace Umbraco.Tests.Testing
{
@@ -29,7 +30,7 @@ namespace Umbraco.Tests.Testing
var methodName = test.MethodName;
var type = Type.GetType(typeName, true);
if (type == null)
- throw new Exception("panic"); // makes no sense
+ throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense
var methodInfo = type.GetMethod(methodName); // what about overloads?
var options = GetTestOptions(methodInfo);
return options;
@@ -53,7 +54,7 @@ namespace Umbraco.Tests.Testing
{
if (other == null) throw new ArgumentNullException(nameof(other));
if (!(Merge((TestOptionAttributeBase) other) is TOptions merged))
- throw new Exception("panic");
+ throw new PanicException("Could not merge test options");
return merged;
}
diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
index 2d2cbaa3fd..7e72a5aefb 100644
--- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
+++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
@@ -248,6 +248,11 @@ namespace Umbraco.Tests.Testing
// register empty content apps collection
Composition.WithCollectionBuilder();
+
+ // manifest
+ Composition.ManifestValueValidators();
+ Composition.ManifestFilters();
+
}
protected virtual void ComposeMapper(bool configure)
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index cc569d6989..fcf73fbffa 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -120,6 +120,7 @@
+
@@ -145,9 +146,9 @@
+
-
@@ -158,6 +159,7 @@
+
@@ -243,6 +245,7 @@
+
@@ -268,6 +271,8 @@
+
+
@@ -452,7 +457,6 @@
-
diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs
new file mode 100644
index 0000000000..3d264663b5
--- /dev/null
+++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Threading;
+using System.Web;
+using System.Web.Hosting;
+using System.Web.Http;
+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.IO;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Mappers;
+using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Persistence.SqlSyntax;
+using Umbraco.Core.Services;
+using Umbraco.Tests.TestHelpers;
+using Umbraco.Tests.TestHelpers.ControllerTesting;
+using Umbraco.Tests.Testing;
+using Umbraco.Web;
+using Umbraco.Web.Editors;
+using Umbraco.Web.Features;
+using Umbraco.Web.Models.ContentEditing;
+using IUser = Umbraco.Core.Models.Membership.IUser;
+
+namespace Umbraco.Tests.Web.Controllers
+{
+ [TestFixture]
+ [UmbracoTest(Database = UmbracoTestOptions.Database.None)]
+ public class AuthenticationControllerTests : TestWithDatabaseBase
+ {
+ protected override void ComposeApplication(bool withApplication)
+ {
+ base.ComposeApplication(withApplication);
+ //if (!withApplication) return;
+
+ // replace the true IUserService implementation with a mock
+ // so that each test can configure the service to their liking
+ Composition.RegisterUnique(f => Mock.Of());
+
+ // kill the true IEntityService too
+ Composition.RegisterUnique(f => Mock.Of());
+
+ Composition.RegisterUnique();
+ }
+
+
+ [Test]
+ public async System.Threading.Tasks.Task GetCurrentUser_Fips()
+ {
+ ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper)
+ {
+ //setup some mocks
+ var userServiceMock = Mock.Get(Current.Services.UserService);
+ userServiceMock.Setup(service => service.GetUserById(It.IsAny()))
+ .Returns(() => null);
+
+ if (Thread.GetDomain().GetData(".appPath") != null)
+ {
+ HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter()));
+ }
+ else
+ {
+ var baseDir = IOHelper.MapPath("", false).TrimEnd(IOHelper.DirSepChar);
+ HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter()));
+ }
+ IOHelper.ForceNotHosted = true;
+ var usersController = new AuthenticationController(
+ Factory.GetInstance(),
+ umbracoContextAccessor,
+ Factory.GetInstance(),
+ Factory.GetInstance(),
+ Factory.GetInstance(),
+ Factory.GetInstance(),
+ Factory.GetInstance(),
+ helper);
+ return usersController;
+ }
+
+ Mock.Get(Current.SqlContext)
+ .Setup(x => x.Query())
+ .Returns(new Query(Current.SqlContext));
+
+ var syntax = new SqlCeSyntaxProvider();
+
+ Mock.Get(Current.SqlContext)
+ .Setup(x => x.SqlSyntax)
+ .Returns(syntax);
+
+ var mappers = new MapperCollection(new[]
+ {
+ new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>())
+ });
+
+ Mock.Get(Current.SqlContext)
+ .Setup(x => x.Mappers)
+ .Returns(mappers);
+
+ // Testing what happens if the system were configured to only use FIPS-compliant algorithms
+ var typ = typeof(CryptoConfig);
+ var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
+ var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
+ var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
+ var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
+
+ try
+ {
+ if (!originalFipsValue)
+ {
+ haveFld.SetValue(null, true);
+ isFld.SetValue(null, true);
+ }
+
+ var runner = new TestRunner(CtrlFactory);
+ var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get);
+
+ var obj = JsonConvert.DeserializeObject(response.Item2);
+ Assert.AreEqual(-1, obj.UserId);
+ }
+ finally
+ {
+ if (!originalFipsValue)
+ {
+ haveFld.SetValue(null, false);
+ isFld.SetValue(null, false);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs
index a4213b4f0e..d77867152a 100644
--- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs
+++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs
@@ -77,6 +77,19 @@ namespace Umbraco.Tests.Web.Controllers
var entityService = new Mock();
entityService.Setup(x => x.GetAllPaths(UmbracoObjectTypes.Document, It.IsAny()))
.Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = $"-1,{x}", Id = x }).ToList());
+ entityService.Setup(x => x.GetKey(It.IsAny(), UmbracoObjectTypes.DataType))
+ .Returns((int id, UmbracoObjectTypes objType) =>
+ {
+ switch (id)
+ {
+ case Constants.DataTypes.Textbox:
+ return Attempt.Succeed(Constants.DataTypes.Guids.TextstringGuid);
+ case Constants.DataTypes.RichtextEditor:
+ return Attempt.Succeed(Constants.DataTypes.Guids.RichtextEditorGuid);
+ }
+ return Attempt.Fail();
+ });
+
var dataTypeService = new Mock();
dataTypeService.Setup(service => service.GetDataType(It.IsAny()))
diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs
index a4c3078b8f..85dd303432 100644
--- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs
+++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs
@@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
+using System.Reflection;
+using System.Security.Cryptography;
using System.Web.Http;
using Moq;
using Newtonsoft.Json;
@@ -155,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers
var runner = new TestRunner(CtrlFactory);
var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get);
- var obj = JsonConvert.DeserializeObject>(response.Item2);
+ var obj = JsonConvert.DeserializeObject>(response.Item2);
Assert.AreEqual(0, obj.TotalItems);
}
@@ -190,9 +192,100 @@ namespace Umbraco.Tests.Web.Controllers
var runner = new TestRunner(CtrlFactory);
var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get);
- var obj = JsonConvert.DeserializeObject>(response.Item2);
+ var obj = JsonConvert.DeserializeObject>(response.Item2);
Assert.AreEqual(10, obj.TotalItems);
Assert.AreEqual(10, obj.Items.Count());
}
+
+ [Test]
+ public async System.Threading.Tasks.Task GetPagedUsers_Fips()
+ {
+ await RunFipsTest("GetPagedUsers", mock =>
+ {
+ var users = MockedUser.CreateMulipleUsers(10);
+ long outVal = 10;
+ mock.Setup(service => service.GetAll(
+ It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(),
+ It.IsAny(), It.IsAny