Merged with latest v8/dev
This commit is contained in:
4
.github/BUILD.md
vendored
4
.github/BUILD.md
vendored
@@ -1,4 +1,4 @@
|
||||
# Umbraco Cms Build
|
||||
# Umbraco CMS Build
|
||||
|
||||
## Are you sure?
|
||||
|
||||
@@ -66,7 +66,7 @@ The Visual Studio object is `null` when Visual Studio has not been detected (eg
|
||||
* `Path`: Visual Studio installation path (eg some place under `Program Files`)
|
||||
* `Major`: Visual Studio major version (eg `15` for VS 2017)
|
||||
* `Minor`: Visual Studio minor version
|
||||
* `MsBUild`: the absolute path to the MsBuild executable
|
||||
* `MsBuild`: the absolute path to the MsBuild executable
|
||||
|
||||
#### GetUmbracoVersion
|
||||
|
||||
|
||||
2
.github/CODE_OF_CONDUCT.md
vendored
2
.github/CODE_OF_CONDUCT.md
vendored
@@ -29,4 +29,4 @@ Don't rest on your laurels and never accept the status quo. Contribute and give
|
||||
|
||||
## Friendly
|
||||
|
||||
Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and builds long lasting relationships.
|
||||
Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and build long lasting relationships.
|
||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -38,7 +38,7 @@ This document gives you a quick overview on how to get started.
|
||||
|
||||
### Guidelines for contributions we welcome
|
||||
|
||||
Not all changes are wanted, so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
|
||||
Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
|
||||
|
||||
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes.
|
||||
|
||||
|
||||
35
.github/CONTRIBUTION_GUIDELINES.md
vendored
Normal file
35
.github/CONTRIBUTION_GUIDELINES.md
vendored
Normal file
@@ -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 to 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 fix bugs.
|
||||
|
||||
Eventually, a package could "graduate" to be included in the CMS.
|
||||
2
.github/README.md
vendored
2
.github/README.md
vendored
@@ -1,4 +1,4 @@
|
||||
# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://pullreminders.com?ref=badge)
|
||||
# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
|
||||
|
||||
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
|
||||
25
.github/REVIEW_PROCESS.md
vendored
Normal file
25
.github/REVIEW_PROCESS.md
vendored
Normal file
@@ -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.
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
not want this to happen as the alpha of the next major is, really, the next major already.
|
||||
-->
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="ClientDependency" version="[1.9.7,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0,1.999999)" />
|
||||
<dependency id="ClientDependency" version="[1.9.8,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.9.3,1.999999)" />
|
||||
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
|
||||
<dependency id="Examine" version="[1.0.0,1.999999)" />
|
||||
<dependency id="Examine" version="[1.0.1,1.999999)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.8.14,1.999999)" />
|
||||
<dependency id="ImageProcessor" version="[2.7.0.100,2.999999)" />
|
||||
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
<dependency id="ImageProcessor.Web" version="[4.10.0.100,4.999999)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.5.0.100,2.999999)" />
|
||||
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
|
||||
<dependency id="Microsoft.Net.Compilers" version="[2.10.0,2.999999)" />
|
||||
|
||||
</group>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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!
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Data.SqlServerCe')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.ValueTuple')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Net.Http.Formatting')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Collections.Immutable')" xdt:Transform="Remove" />
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -456,25 +456,24 @@
|
||||
$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
|
||||
$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",
|
||||
|
||||
@@ -1,65 +1,11 @@
|
||||
body {
|
||||
color: rgba(0,0,0,.8);
|
||||
}
|
||||
.navbar-inverse {
|
||||
background: #a3db78;
|
||||
}
|
||||
.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text {
|
||||
color: rgba(0,0,0,.8);
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.sidetoc {
|
||||
background-color: #f5fbf1;
|
||||
}
|
||||
body .toc {
|
||||
background-color: #f5fbf1;
|
||||
}
|
||||
.sidefilter {
|
||||
background-color: #daf0c9;
|
||||
}
|
||||
.subnav {
|
||||
background-color: #f5fbf1;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a {
|
||||
color: rgba(0,0,0,.8);
|
||||
background-color: #daf0c9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover {
|
||||
color: rgba(0,0,0,.8);
|
||||
background-color: #daf0c9;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: rgba(0,0,0,.8);
|
||||
background-color: #fff;
|
||||
border-color: rgba(0,0,0,.8);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #daf0c9;
|
||||
color: rgba(0,0,0,.8);
|
||||
border-color: rgba(0,0,0,.8);
|
||||
}
|
||||
|
||||
.toc .nav > li > a {
|
||||
color: rgba(0,0,0,.8);
|
||||
}
|
||||
|
||||
button, a {
|
||||
color: #f36f21;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus,
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: #143653;
|
||||
text-decoration: none;
|
||||
color: #34393e;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
word-wrap: break-word
|
||||
}
|
||||
|
||||
.navbar-header .navbar-brand {
|
||||
@@ -68,6 +14,202 @@ a:focus {
|
||||
width:50px;
|
||||
}
|
||||
|
||||
.toc .nav > li.active > a {
|
||||
color: #f36f21;
|
||||
#_content>a {
|
||||
margin-top: 5px;
|
||||
}
|
||||
/* HEADINGS */
|
||||
|
||||
h1 {
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
article h1,
|
||||
article h2,
|
||||
article h3,
|
||||
article h4 {
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
article h4 {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
/* NAVBAR */
|
||||
|
||||
.navbar-brand>img {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border: none;
|
||||
/* Both navbars use box-shadow */
|
||||
-webkit-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5);
|
||||
-moz-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5);
|
||||
box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5);
|
||||
}
|
||||
|
||||
.subnav {
|
||||
border-top: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
background-color: #303ea1;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a,
|
||||
.navbar-inverse .navbar-text {
|
||||
color: #fff;
|
||||
background-color: #303ea1;
|
||||
border-bottom: 3px solid transparent;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a:focus,
|
||||
.navbar-inverse .navbar-nav>li>a:hover {
|
||||
color: #fff;
|
||||
background-color: #303ea1;
|
||||
border-bottom: 3px solid white;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a,
|
||||
.navbar-inverse .navbar-nav>.active>a:focus,
|
||||
.navbar-inverse .navbar-nav>.active>a:hover {
|
||||
color: #fff;
|
||||
background-color: #303ea1;
|
||||
border-bottom: 3px solid white;
|
||||
}
|
||||
|
||||
.navbar-form .form-control {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
/* SIDEBAR */
|
||||
|
||||
.toc .level1>li {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.toc .nav>li>a {
|
||||
color: #34393e;
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: #fff;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: #fff;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.toc-filter {
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toc-filter>input {
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.toc-filter>.filter-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidetoc>.toc {
|
||||
background-color: #fff;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sidetoc {
|
||||
background-color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* ALERTS */
|
||||
|
||||
.alert {
|
||||
padding: 0px 0px 5px 0px;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
box-shadow: 0px 2px 2px 0px rgba(100, 100, 100, 0.4);
|
||||
}
|
||||
|
||||
.alert>p {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.alert>ul {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 40px;
|
||||
}
|
||||
|
||||
.alert>h5 {
|
||||
padding: 10px 15px;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.alert-info>h5 {
|
||||
color: #1976d2;
|
||||
border-bottom: 4px solid #1976d2;
|
||||
background-color: #e3f2fd;
|
||||
}
|
||||
|
||||
.alert-warning>h5 {
|
||||
color: #f57f17;
|
||||
border-bottom: 4px solid #f57f17;
|
||||
background-color: #fff3e0;
|
||||
}
|
||||
|
||||
.alert-danger>h5 {
|
||||
color: #d32f2f;
|
||||
border-bottom: 4px solid #d32f2f;
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
/* CODE HIGHLIGHT */
|
||||
pre {
|
||||
padding: 9.5px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: #fffaef;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 1px 4px 1px rgba(100, 100, 100, 0.4);
|
||||
}
|
||||
|
||||
.sideaffix {
|
||||
overflow: visible;
|
||||
}
|
||||
@@ -18,5 +18,5 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("8.0.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.2.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.2.0")]
|
||||
[assembly: AssemblyFileVersion("8.3.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.3.0")]
|
||||
|
||||
@@ -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<IMediaFileSystem>() returns the underlying filesystem
|
||||
composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
|
||||
composition.SetMediaFileSystem(() => new PhysicalFileSystem(SystemDirectories.Media));
|
||||
|
||||
return composition;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
|
||||
var pluginLangFolders = appPlugins.Exists == false
|
||||
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
|
||||
: 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));
|
||||
|
||||
|
||||
@@ -12,31 +12,62 @@
|
||||
/// or MS.DI, PerDependency in Autofac.</remarks>
|
||||
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'?
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per request.
|
||||
/// </summary>
|
||||
// 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?
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Any instance created with this lifetime will be disposed at the end of a request.
|
||||
/// </para>
|
||||
/// Corresponds to
|
||||
/// <para>
|
||||
/// 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
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 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</para>
|
||||
/// <para>
|
||||
/// 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."
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 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
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Request,
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per container scope.
|
||||
/// One unique instance per scope.
|
||||
/// </summary>
|
||||
/// <remarks>Corresponds to Scope in LightInject, Scoped in MS.DI
|
||||
/// or Castle Windsor, PerLifetimeScope in Autofac.</remarks>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Any instance created with this lifetime will be disposed at the end of the current scope.
|
||||
/// </para>
|
||||
/// Corresponds to
|
||||
/// <para>PerScopeLifetime in LightInject (when in a request, means one per web request)</para>
|
||||
/// <para>
|
||||
/// 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</para>
|
||||
/// <para>
|
||||
/// 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."
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// LifestyleScoped in Castle Windsor
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Scope,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace Umbraco.Core.Configuration
|
||||
/// </summary>
|
||||
private static void ResetInternal()
|
||||
{
|
||||
GlobalSettingsExtensions.Reset();
|
||||
_reservedPaths = null;
|
||||
_reservedUrls = null;
|
||||
HasSmtpServer = null;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in unit testing to reset all config items, this is automatically called by GlobalSettings.Reset()
|
||||
/// </summary>
|
||||
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<string> _reservedList = new HashSet<string>();
|
||||
private static string _mvcArea;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This returns the string of the MVC Area route.
|
||||
/// </summary>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="url">The URL to check.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
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<string>();
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="routes">The route collection to lookup the request in</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,17 +85,17 @@ namespace Umbraco.Core
|
||||
/// ListView.
|
||||
/// </summary>
|
||||
public const string ListView = "Umbraco.ListView";
|
||||
|
||||
/// <summary>
|
||||
/// Macro Container.
|
||||
/// </summary>
|
||||
public const string MacroContainer = "Umbraco.MacroContainer";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Media Picker.
|
||||
/// </summary>
|
||||
public const string MediaPicker = "Umbraco.MediaPicker";
|
||||
|
||||
/// <summary>
|
||||
/// Multiple Media Picker.
|
||||
/// </summary>
|
||||
public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker";
|
||||
|
||||
/// <summary>
|
||||
/// Member Picker.
|
||||
/// </summary>
|
||||
@@ -203,6 +203,24 @@ namespace Umbraco.Core
|
||||
/// <remarks>Must be a valid <see cref="ValueTypes"/> value.</remarks>
|
||||
public const string DataValueType = "umbracoDataValueType";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines Umbraco's built-in property editor groups.
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,39 +150,46 @@ namespace Umbraco.Core
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
segment = segment.NullOrWhiteSpaceAsNull();
|
||||
|
||||
bool Validate(bool variesBy, string value)
|
||||
{
|
||||
if (variesBy)
|
||||
{
|
||||
// varies by
|
||||
// in exact mode, the value cannot be null (but it can be a wildcard)
|
||||
// in !wildcards mode, the value cannot be a wildcard (but it can be null)
|
||||
if ((exact && value == null) || (!wildcards && value == "*"))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// does not vary by value
|
||||
// the value cannot have a value
|
||||
// unless wildcards and it's "*"
|
||||
if (value != null && (!wildcards || value != "*"))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Validate(variation.VariesByCulture(), culture))
|
||||
// if wildcards are disabled, do not allow "*"
|
||||
if (!wildcards && (culture == "*" || segment == "*"))
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Culture value \"{culture ?? "<null>"}\" is invalid.");
|
||||
throw new NotSupportedException($"Variation wildcards are not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Validate(variation.VariesBySegment(), segment))
|
||||
if (variation.VariesByCulture())
|
||||
{
|
||||
// varies by culture
|
||||
// in exact mode, the culture cannot be null
|
||||
if (exact && culture == null)
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Culture may not be null because culture variation is enabled.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// does not vary by culture
|
||||
// the culture cannot have a value
|
||||
// unless wildcards and it's "*"
|
||||
if (culture != null && !(wildcards && culture == "*"))
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if it does not vary by segment
|
||||
// the segment cannot have a value
|
||||
// segment may always be null, even when the ContentVariation.Segment flag is set for this variation,
|
||||
// therefore the exact parameter is not used in segment validation.
|
||||
if (!variation.VariesBySegment() && segment != null && !(wildcards && segment == "*"))
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Segment value \"{segment ?? "<null>"}\" is invalid.");
|
||||
throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
/// <returns></returns>
|
||||
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)
|
||||
|
||||
28
src/Umbraco.Core/Exceptions/PanicException.cs
Normal file
28
src/Umbraco.Core/Exceptions/PanicException.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen
|
||||
/// </summary>
|
||||
[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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -42,6 +42,12 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
|
||||
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Serilog minimum log level
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetLogLevel();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the collection of logs
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetLogLevel()
|
||||
{
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.Logger.IsEnabled)?.Min() ?? null;
|
||||
return logLevel?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var counter = new CountingFilter();
|
||||
|
||||
@@ -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<TTarget>((IEnumerable) s, targetGenericArg, ctor, map, c);
|
||||
object NCtor(object s, MapperContext c) => MapEnumerableInternal<TTarget>((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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -342,7 +343,21 @@ namespace Umbraco.Core.Mapping
|
||||
|
||||
if (ctor == null) return null;
|
||||
|
||||
_ctors[sourceType] = sourceCtor;
|
||||
_ctors.AddOrUpdate(sourceType, sourceCtor, (k, v) =>
|
||||
{
|
||||
// Add missing constructors
|
||||
foreach (var c in sourceCtor)
|
||||
{
|
||||
if (!v.ContainsKey(c.Key))
|
||||
{
|
||||
v.Add(c.Key, c.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
});
|
||||
|
||||
|
||||
return ctor;
|
||||
}
|
||||
|
||||
@@ -367,7 +382,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 +407,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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -226,8 +227,8 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_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 });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PropertyTypeDto>(Sql()
|
||||
.Select<PropertyTypeDto>(x => x.Id, x => x.Alias)
|
||||
.From<PropertyTypeDto>()
|
||||
.Where<PropertyTypeDto>(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<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelInt)).WhereIn<PropertyTypeDto>(x => x.Id, intPropertyTypes));
|
||||
Database.Execute(Sql().Update<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelInt)).WhereIn<PropertyTypeDto>(x => x.Id, intPropertyTypes));
|
||||
Database.Execute(Sql().Update<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelBigint)).WhereIn<PropertyTypeDto>(x => x.Id, bigintPropertyTypes));
|
||||
Database.Execute(Sql().Update<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelDateTime)).WhereIn<PropertyTypeDto>(x => x.Id, dtPropertyTypes));
|
||||
|
||||
@@ -72,6 +72,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
var sqlNodeData = Sql()
|
||||
.Select<NodeDto>()
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeId == intId);
|
||||
|
||||
var node = Database.Fetch<NodeDto>(sqlNodeData).FirstOrDefault();
|
||||
|
||||
@@ -74,9 +74,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(x => x.NodeId == group.Key)).First();
|
||||
|
||||
// check for duplicate aliases
|
||||
var aliases = group.Select(x => x.Alias).Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
|
||||
if (aliases.Distinct().Count() != aliases.Length)
|
||||
throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataType.NodeId}, editor={dataType.EditorAlias}: duplicate alias.");
|
||||
|
||||
// handle null/empty aliases
|
||||
int index = 0;
|
||||
var dictionary = group.ToDictionary(x => string.IsNullOrWhiteSpace(x.Alias) ? index++.ToString() : x.Alias);
|
||||
|
||||
// migrate the preValues to configuration
|
||||
var migrator = _preValueMigrators.GetMigrator(dataType.EditorAlias) ?? new DefaultPreValueMigrator();
|
||||
var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, group.ToDictionary(x => x.Alias, x => x));
|
||||
var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, dictionary);
|
||||
var json = JsonConvert.SerializeObject(config);
|
||||
|
||||
// validate - and kill the migration if it fails
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
}
|
||||
|
||||
// assuming we don't want to fall back to array
|
||||
if (aliases.Length != preValuesA.Count || aliases.Any(string.IsNullOrWhiteSpace))
|
||||
throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataTypeId} preValues: duplicate or null/empty alias.");
|
||||
if (aliases.Any(string.IsNullOrWhiteSpace))
|
||||
throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataTypeId}, editor={editorAlias}: null/empty alias.");
|
||||
|
||||
// dictionary-base prevalues
|
||||
return GetPreValues(preValuesA).ToDictionary(x => x.Alias, GetPreValueValue);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@ public class PreValueMigratorComposer : ICoreComposer
|
||||
.Append<DecimalPreValueMigrator>()
|
||||
.Append<ListViewPreValueMigrator>()
|
||||
.Append<DropDownFlexiblePreValueMigrator>()
|
||||
.Append<ValueListPreValueMigrator>();
|
||||
.Append<ValueListPreValueMigrator>()
|
||||
.Append<MarkdownEditorPreValueMigrator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MacroDto>("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<MacroDto>("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
|
||||
|
||||
@@ -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;");
|
||||
|
||||
@@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c))
|
||||
foreach (var control in allControls.SelectMany(c => c).OfType<JObject>())
|
||||
{
|
||||
var controlValue = control["value"];
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
if (controlValue?.Type == JTokenType.String)
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
}
|
||||
|
||||
@@ -222,7 +222,13 @@ namespace Umbraco.Core.Models
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void UnpublishCulture(this IContent content, string culture = "*")
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
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)
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
7
src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs
Normal file
7
src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
public interface IMemberEntitySlim : IContentEntitySlim
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
13
src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs
Normal file
13
src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
public class MemberEntitySlim : EntitySlim, IMemberEntitySlim
|
||||
{
|
||||
public string ContentTypeAlias { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentTypeIcon { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentTypeThumbnail { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
|
||||
PropertyGroupCollection PropertyGroups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
IEnumerable<PropertyType> PropertyTypes { get; }
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ namespace Umbraco.Core.Models.Membership
|
||||
return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections, group.Permissions);
|
||||
}
|
||||
|
||||
public static bool IsSystemUserGroup(this IUserGroup group) =>
|
||||
IsSystemUserGroup(group.Alias);
|
||||
|
||||
public static bool IsSystemUserGroup(this IReadOnlyUserGroup group) =>
|
||||
IsSystemUserGroup(group.Alias);
|
||||
|
||||
public static IReadOnlyUserGroup ToReadOnlyGroup(this UserGroupDto group)
|
||||
{
|
||||
return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon,
|
||||
@@ -22,5 +28,12 @@ namespace Umbraco.Core.Models.Membership
|
||||
group.UserGroup2AppDtos.Select(x => x.AppAlias).ToArray(),
|
||||
group.DefaultPermissions == null ? Enumerable.Empty<string>() : group.DefaultPermissions.ToCharArray().Select(x => x.ToString()));
|
||||
}
|
||||
|
||||
private static bool IsSystemUserGroup(this string groupAlias)
|
||||
{
|
||||
return groupAlias == Constants.Security.AdminGroupAlias
|
||||
|| groupAlias == Constants.Security.SensitiveDataGroupAlias
|
||||
|| groupAlias == Constants.Security.TranslatorGroupAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,16 @@
|
||||
/// <remarks>Is used by <see cref="PublishedContentType"/> constructor to create special property types.</remarks>
|
||||
IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a core (non-user) published property type.
|
||||
/// </summary>
|
||||
/// <param name="contentType">The published content type owning the property.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <param name="dataTypeId">The datatype identifier.</param>
|
||||
/// <param name="variations">The variations.</param>
|
||||
/// <remarks>Is used by <see cref="PublishedContentType"/> constructor to create special property types.</remarks>
|
||||
IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a published datatype.
|
||||
/// </summary>
|
||||
|
||||
@@ -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('`'));
|
||||
|
||||
@@ -92,26 +92,26 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
var aliases = new HashSet<string>(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<string, (int, string)> BuiltinMemberProperties = new Dictionary<string, (int, string)>
|
||||
private static readonly Dictionary<string, int> BuiltinMemberProperties = new Dictionary<string, int>
|
||||
{
|
||||
{ "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
|
||||
|
||||
@@ -61,6 +61,12 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing)
|
||||
{
|
||||
return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is for tests and is not intended to be used directly from application code.
|
||||
/// </summary>
|
||||
|
||||
@@ -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<IDataType>();
|
||||
var removedLanguages = new List<ILanguage>();
|
||||
|
||||
|
||||
//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<IContentType>();
|
||||
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<IContentType>();
|
||||
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<IContent> ImportContent(IEnumerable<CompiledPackageDocument> docs, IDictionary<string, IContentType> importedDocumentTypes, int userId)
|
||||
public IReadOnlyList<IContent> ImportContent(IEnumerable<CompiledPackageDocument> docs, IDictionary<string, IContentType> importedDocumentTypes, int userId)
|
||||
{
|
||||
return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId));
|
||||
return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -353,7 +386,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
#region DocumentTypes
|
||||
|
||||
public IEnumerable<IContentType> ImportDocumentType(XElement docTypeElement, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentType(XElement docTypeElement, int userId)
|
||||
{
|
||||
return ImportDocumentTypes(new[] { docTypeElement }, userId);
|
||||
}
|
||||
@@ -364,7 +397,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="docTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
public IEnumerable<IContentType> ImportDocumentTypes(IEnumerable<XElement> docTypeElements, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentTypes(IEnumerable<XElement> docTypeElements, int userId)
|
||||
{
|
||||
return ImportDocumentTypes(docTypeElements.ToList(), true, userId);
|
||||
}
|
||||
@@ -376,7 +409,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="importStructure">Boolean indicating whether or not to import the </param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
public IEnumerable<IContentType> ImportDocumentTypes(IReadOnlyCollection<XElement> unsortedDocumentTypes, bool importStructure, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentTypes(IReadOnlyCollection<XElement> unsortedDocumentTypes, bool importStructure, int userId)
|
||||
{
|
||||
var importedContentTypes = new Dictionary<string, IContentType>();
|
||||
|
||||
@@ -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,9 +784,14 @@ 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,
|
||||
Mandatory = property.Element("Mandatory") != null
|
||||
? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true")
|
||||
: false,
|
||||
ValidationRegExp = (string)property.Element("Validation"),
|
||||
SortOrder = sortOrder
|
||||
SortOrder = sortOrder,
|
||||
Variations = property.Element("Variations") != null
|
||||
? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value)
|
||||
: ContentVariation.Nothing
|
||||
};
|
||||
|
||||
var tab = (string)property.Element("Tab");
|
||||
@@ -817,7 +858,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="dataTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the user</param>
|
||||
/// <returns>An enumerable list of generated DataTypeDefinitions</returns>
|
||||
public IEnumerable<IDataType> ImportDataTypes(IReadOnlyCollection<XElement> dataTypeElements, int userId)
|
||||
public IReadOnlyList<IDataType> ImportDataTypes(IReadOnlyCollection<XElement> dataTypeElements, int userId)
|
||||
{
|
||||
var dataTypes = new List<IDataType>();
|
||||
|
||||
@@ -946,13 +987,13 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="dictionaryItemElementList">Xml to import</param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns>An enumerable list of dictionary items</returns>
|
||||
public IEnumerable<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, int userId)
|
||||
public IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, int userId)
|
||||
{
|
||||
var languages = _localizationService.GetAllLanguages().ToList();
|
||||
return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId);
|
||||
}
|
||||
|
||||
private IEnumerable<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, List<ILanguage> languages, Guid? parentId, int userId)
|
||||
private IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, List<ILanguage> languages, Guid? parentId, int userId)
|
||||
{
|
||||
var items = new List<IDictionaryItem>();
|
||||
foreach (var dictionaryItemElement in dictionaryItemElementList)
|
||||
@@ -1029,7 +1070,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="languageElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation</param>
|
||||
/// <returns>An enumerable list of generated languages</returns>
|
||||
public IEnumerable<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId)
|
||||
public IReadOnlyList<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId)
|
||||
{
|
||||
var list = new List<ILanguage>();
|
||||
foreach (var languageElement in languageElements)
|
||||
@@ -1058,7 +1099,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="macroElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IMacro> ImportMacros(IEnumerable<XElement> macroElements, int userId)
|
||||
public IReadOnlyList<IMacro> ImportMacros(IEnumerable<XElement> macroElements, int userId)
|
||||
{
|
||||
var macros = macroElements.Select(ParseMacroElement).ToList();
|
||||
|
||||
@@ -1148,7 +1189,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
#region Stylesheets
|
||||
|
||||
public IEnumerable<IFile> ImportStylesheets(IEnumerable<XElement> stylesheetElements, int userId)
|
||||
public IReadOnlyList<IFile> ImportStylesheets(IEnumerable<XElement> stylesheetElements, int userId)
|
||||
{
|
||||
var result = new List<IFile>();
|
||||
|
||||
@@ -1216,7 +1257,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="templateElements">Xml to import</param>
|
||||
/// <param name="userId">Optional user id</param>
|
||||
/// <returns>An enumerable list of generated Templates</returns>
|
||||
public IEnumerable<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
public IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
{
|
||||
var templates = new List<ITemplate>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
public static IEnumerable<Property> BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection<PropertyDataDto> dtos, int publishedVersionId, ILanguageRepository languageRepository)
|
||||
{
|
||||
var properties = new List<Property>();
|
||||
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>) x);
|
||||
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>)x);
|
||||
|
||||
foreach (var propertyType in propertyTypes)
|
||||
{
|
||||
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
/// <param name="properties">The properties to map</param>
|
||||
/// <param name="languageRepository"></param>
|
||||
/// <param name="edited">out parameter indicating that one or more properties have been edited</param>
|
||||
/// <param name="editedCultures">out parameter containing a collection of edited cultures when the contentVariation varies by culture</param>
|
||||
/// <param name="editedCultures">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable<Property> properties,
|
||||
ILanguageRepository languageRepository, out bool edited, out HashSet<string> editedCultures)
|
||||
ILanguageRepository languageRepository, out bool edited,
|
||||
out HashSet<string> editedCultures)
|
||||
{
|
||||
var propertyDataDtos = new List<PropertyDataDto>();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
totalRecords = page.TotalItems;
|
||||
|
||||
var items = page.Items.Select(
|
||||
dto => new AuditItem(dto.Id, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
|
||||
dto => new AuditItem(dto.NodeId, Enum<AuditType>.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++)
|
||||
|
||||
@@ -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<string, TagConfiguration>();
|
||||
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<TagConfiguration>(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<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos, Dictionary<string, TagConfiguration> tagConfigurations)
|
||||
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos)
|
||||
where T : class, IContentBase
|
||||
{
|
||||
var result = new Dictionary<int, PropertyCollection>();
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -90,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
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>, 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;
|
||||
@@ -56,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<IContentType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<TEntity> : NPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
|
||||
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<MoveEventInfo<TEntity>> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
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<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
|
||||
{
|
||||
var impact = new List<IContentTypeComposition>();
|
||||
@@ -525,12 +550,12 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
var tree = new Dictionary<int, List<IContentTypeComposition>>();
|
||||
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<IContentTypeComposition>();
|
||||
list.Add(x);
|
||||
}
|
||||
foreach (var y in x.ContentTypeComposition)
|
||||
{
|
||||
if (!tree.TryGetValue(y.Id, out var list))
|
||||
list = tree[y.Id] = new List<IContentTypeComposition>();
|
||||
list.Add(x);
|
||||
}
|
||||
|
||||
var nset = new List<IContentTypeComposition>();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeIds"></param>
|
||||
/// <param name="contentTypeIds"></param>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> 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<PropertyDataDto>()
|
||||
.AndSelect<ContentVersionDto>(x => x.NodeId, x => x.Current)
|
||||
.AndSelect<DocumentVersionDto>(x => x.Published)
|
||||
.AndSelect<PropertyTypeDto>(x => x.Variations)
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, PropertyDataDto>((left, right) => left.Id == right.VersionId)
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((left, right) => left.Id == right.PropertyTypeId);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
{
|
||||
propertySql.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>((c, cversion) => c.NodeId == cversion.NodeId);
|
||||
}
|
||||
|
||||
propertySql.LeftJoin<DocumentVersionDto>().On<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Id == docversion.Id)
|
||||
.Where<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Current || docversion.Published)
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
{
|
||||
propertySql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
}
|
||||
|
||||
propertySql
|
||||
.OrderBy<ContentVersionDto>(x => x.NodeId)
|
||||
.OrderBy<PropertyDataDto>(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<int, bool>();
|
||||
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<PropertyValueVersionDto>(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<DocumentCultureVariationDto>(
|
||||
Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
|
||||
.WhereIn<DocumentCultureVariationDto>(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<DocumentCultureVariationDto>();
|
||||
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<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentCultureVariationDto>(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<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentDto>(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)
|
||||
|
||||
@@ -248,14 +248,63 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return dto == null ? null : MapDtoToContent(dto);
|
||||
}
|
||||
|
||||
// deletes a specific version
|
||||
public override void DeleteVersion(int versionId)
|
||||
{
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the version we want to delete
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersion", tsql =>
|
||||
tsql.Select<ContentVersionDto>()
|
||||
.AndSelect<DocumentVersionDto>()
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<DocumentVersionDto>()
|
||||
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
|
||||
.Where<ContentVersionDto>(x => x.Id == SqlTemplate.Arg<int>("versionId"))
|
||||
);
|
||||
var versionDto = Database.Fetch<DocumentVersionDto>(template.Sql(new { versionId })).FirstOrDefault();
|
||||
|
||||
// nothing to delete
|
||||
if (versionDto == null)
|
||||
return;
|
||||
|
||||
// don't delete the current or published version
|
||||
if (versionDto.ContentVersionDto.Current)
|
||||
throw new InvalidOperationException("Cannot delete the current version.");
|
||||
else if (versionDto.Published)
|
||||
throw new InvalidOperationException("Cannot delete the published version.");
|
||||
|
||||
PerformDeleteVersion(versionDto.ContentVersionDto.NodeId, versionId);
|
||||
}
|
||||
|
||||
// deletes all versions of an entity, older than a date.
|
||||
public override void DeleteVersions(int nodeId, DateTime versionDate)
|
||||
{
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the versions we want to delete, excluding the current one
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersions", tsql =>
|
||||
tsql.Select<ContentVersionDto>()
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<DocumentVersionDto>()
|
||||
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
|
||||
.Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg<DateTime>("versionDate"))
|
||||
.Where<DocumentVersionDto>( x => !x.Published)
|
||||
);
|
||||
var versionDtos = Database.Fetch<ContentVersionDto>(template.Sql(new { nodeId, versionDate }));
|
||||
foreach (var versionDto in versionDtos)
|
||||
PerformDeleteVersion(versionDto.NodeId, versionDto.Id);
|
||||
}
|
||||
|
||||
protected override void PerformDeleteVersion(int id, int versionId)
|
||||
{
|
||||
// raise event first else potential FK issues
|
||||
OnUowRemovingVersion(new ScopedVersionEventArgs(AmbientScope, id, versionId));
|
||||
|
||||
Database.Delete<PropertyDataDto>("WHERE versionId = @versionId", new { versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE id = @versionId", new { versionId });
|
||||
Database.Delete<ContentVersionCultureVariationDto>("WHERE versionId = @versionId", new { versionId });
|
||||
Database.Delete<DocumentVersionDto>("WHERE id = @versionId", new { versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE id = @versionId", new { versionId });
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -386,7 +435,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
@@ -511,7 +560,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
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)
|
||||
@@ -571,7 +620,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
@@ -1297,25 +1346,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, HashSet<string> 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
|
||||
|
||||
@@ -42,8 +42,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
var isMember = objectType == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, x =>
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, x =>
|
||||
{
|
||||
if (filter == null) return;
|
||||
foreach (var filterClause in filter.GetWhereClauses())
|
||||
@@ -54,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
|
||||
sql = translator.Translate();
|
||||
sql = AddGroupBy(isContent, isMedia, sql, ordering.IsEmpty);
|
||||
sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty);
|
||||
|
||||
if (!ordering.IsEmpty)
|
||||
{
|
||||
@@ -81,6 +82,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
dtos = page.Items;
|
||||
totalRecords = page.TotalItems;
|
||||
}
|
||||
else if (isMember)
|
||||
{
|
||||
var page = Database.Page<MemberEntityDto>(pageIndexToFetch, pageSize, sql);
|
||||
dtos = page.Items;
|
||||
totalRecords = page.TotalItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
var page = Database.Page<BaseDto>(pageIndexToFetch, pageSize, sql);
|
||||
@@ -88,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
totalRecords = page.TotalItems;
|
||||
}
|
||||
|
||||
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray();
|
||||
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray();
|
||||
|
||||
if (isContent)
|
||||
BuildVariants(entities.Cast<DocumentEntitySlim>());
|
||||
@@ -98,13 +105,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public IEntitySlim Get(Guid key)
|
||||
{
|
||||
var sql = GetBaseWhere(false, false, false, key);
|
||||
var sql = GetBaseWhere(false, false, false, false, key);
|
||||
var dto = Database.FirstOrDefault<BaseDto>(sql);
|
||||
return dto == null ? null : BuildEntity(false, false, dto);
|
||||
return dto == null ? null : BuildEntity(false, false, false, dto);
|
||||
}
|
||||
|
||||
|
||||
private IEntitySlim GetEntity(Sql<ISqlContext> sql, bool isContent, bool isMedia)
|
||||
private IEntitySlim GetEntity(Sql<ISqlContext> sql, bool isContent, bool isMedia, bool isMember)
|
||||
{
|
||||
//isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
if (isContent)
|
||||
@@ -120,7 +127,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
if (dto == null) return null;
|
||||
|
||||
var entity = BuildEntity(false, isMedia, dto);
|
||||
var entity = BuildEntity(false, isMedia, isMember, dto);
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -129,25 +136,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectTypeId == Constants.ObjectTypes.Media;
|
||||
var isMember = objectTypeId == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, key);
|
||||
return GetEntity(sql, isContent, isMedia);
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, key);
|
||||
return GetEntity(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public IEntitySlim Get(int id)
|
||||
{
|
||||
var sql = GetBaseWhere(false, false, false, id);
|
||||
var sql = GetBaseWhere(false, false, false, false, id);
|
||||
var dto = Database.FirstOrDefault<BaseDto>(sql);
|
||||
return dto == null ? null : BuildEntity(false, false, dto);
|
||||
return dto == null ? null : BuildEntity(false, false, false, dto);
|
||||
}
|
||||
|
||||
public IEntitySlim Get(int id, Guid objectTypeId)
|
||||
{
|
||||
var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectTypeId == Constants.ObjectTypes.Media;
|
||||
var isMember = objectTypeId == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, id);
|
||||
return GetEntity(sql, isContent, isMedia);
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, id);
|
||||
return GetEntity(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids)
|
||||
@@ -164,7 +173,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
: PerformGetAll(objectType);
|
||||
}
|
||||
|
||||
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia)
|
||||
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia, bool isMember)
|
||||
{
|
||||
//isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
if (isContent)
|
||||
@@ -180,7 +189,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
? (IEnumerable<BaseDto>)Database.Fetch<MediaEntityDto>(sql)
|
||||
: Database.Fetch<BaseDto>(sql);
|
||||
|
||||
var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray();
|
||||
var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray();
|
||||
|
||||
return entities;
|
||||
}
|
||||
@@ -189,9 +198,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
var isMember = objectType == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter);
|
||||
return GetEntities(sql, isContent, isMedia);
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectType, filter);
|
||||
return GetEntities(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public IEnumerable<TreeEntityPath> GetAllPaths(Guid objectType, params int[] ids)
|
||||
@@ -218,26 +228,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query)
|
||||
{
|
||||
var sqlClause = GetBase(false, false, null);
|
||||
var sqlClause = GetBase(false, false, false, null);
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
sql = AddGroupBy(false, false, sql, true);
|
||||
sql = AddGroupBy(false, false, false, sql, true);
|
||||
var dtos = Database.Fetch<BaseDto>(sql);
|
||||
return dtos.Select(x => BuildEntity(false, false, x)).ToList();
|
||||
return dtos.Select(x => BuildEntity(false, false, false, x)).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType)
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
var isMember = objectType == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, null, objectType);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType);
|
||||
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
|
||||
sql = translator.Translate();
|
||||
sql = AddGroupBy(isContent, isMedia, sql, true);
|
||||
sql = AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
|
||||
return GetEntities(sql, isContent, isMedia);
|
||||
return GetEntities(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public UmbracoObjectTypes GetObjectType(int id)
|
||||
@@ -329,29 +340,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
// gets the full sql for a given object type and a given unique id
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId)
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Guid uniqueId)
|
||||
{
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, objectType, uniqueId);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, uniqueId);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the full sql for a given object type and a given node id
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, int nodeId)
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, int nodeId)
|
||||
{
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, objectType, nodeId);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, nodeId);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the full sql for a given object type, with a given filter
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Action<Sql<ISqlContext>> filter)
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action<Sql<ISqlContext>> filter)
|
||||
{
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, filter, objectType);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM [+ filter] sql
|
||||
// always from the 'current' content version
|
||||
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, Action<Sql<ISqlContext>> filter, bool isCount = false)
|
||||
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, bool isMember, Action<Sql<ISqlContext>> filter, bool isCount = false)
|
||||
{
|
||||
var sql = Sql();
|
||||
|
||||
@@ -366,7 +377,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
.AndSelect<NodeDto>(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate)
|
||||
.Append(", COUNT(child.id) AS children");
|
||||
|
||||
if (isContent || isMedia)
|
||||
if (isContent || isMedia || isMember)
|
||||
sql
|
||||
.AndSelect<ContentVersionDto>(x => Alias(x.Id, "versionId"))
|
||||
.AndSelect<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
|
||||
@@ -387,7 +398,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
sql
|
||||
.From<NodeDto>();
|
||||
|
||||
if (isContent || isMedia)
|
||||
if (isContent || isMedia || isMember)
|
||||
{
|
||||
sql
|
||||
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
|
||||
@@ -404,7 +415,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (isMedia)
|
||||
{
|
||||
sql
|
||||
.InnerJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
.LeftJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
}
|
||||
|
||||
//Any LeftJoin statements need to come last
|
||||
@@ -422,49 +433,49 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// gets the base SELECT + FROM [+ filter] + WHERE sql
|
||||
// for a given object type, with a given filter
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
|
||||
{
|
||||
return GetBase(isContent, isMedia, filter, isCount)
|
||||
return GetBase(isContent, isMedia, isMember, filter, isCount)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == objectType);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given node id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, int id)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, int id)
|
||||
{
|
||||
var sql = GetBase(isContent, isMedia, null, isCount)
|
||||
var sql = GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.NodeId == id);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given unique id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid uniqueId)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid uniqueId)
|
||||
{
|
||||
var sql = GetBase(isContent, isMedia, null, isCount)
|
||||
var sql = GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.UniqueId == uniqueId);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given object type and node id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, int nodeId)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, int nodeId)
|
||||
{
|
||||
return GetBase(isContent, isMedia, null, isCount)
|
||||
return GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.NodeId == nodeId && x.NodeObjectType == objectType);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given object type and unique id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, Guid uniqueId)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, Guid uniqueId)
|
||||
{
|
||||
return GetBase(isContent, isMedia, null, isCount)
|
||||
return GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.UniqueId == uniqueId && x.NodeObjectType == objectType);
|
||||
}
|
||||
|
||||
// gets the GROUP BY / ORDER BY sql
|
||||
// required in order to count children
|
||||
protected Sql<ISqlContext> AddGroupBy(bool isContent, bool isMedia, Sql<ISqlContext> sql, bool defaultSort)
|
||||
protected Sql<ISqlContext> AddGroupBy(bool isContent, bool isMedia, bool isMember, Sql<ISqlContext> sql, bool defaultSort)
|
||||
{
|
||||
sql
|
||||
.GroupBy<NodeDto>(x => x.NodeId, x => x.Trashed, x => x.ParentId, x => x.UserId, x => x.Level, x => x.Path)
|
||||
@@ -483,7 +494,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
|
||||
if (isContent || isMedia)
|
||||
if (isContent || isMedia || isMember)
|
||||
sql
|
||||
.AndBy<ContentVersionDto>(x => x.Id)
|
||||
.AndBy<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
|
||||
@@ -528,6 +539,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
public string MediaPath { get; set; }
|
||||
}
|
||||
|
||||
private class MemberEntityDto : BaseDto
|
||||
{
|
||||
}
|
||||
|
||||
public class VariantInfoDto
|
||||
{
|
||||
public int NodeId { get; set; }
|
||||
@@ -574,12 +589,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
#region Factory
|
||||
|
||||
private EntitySlim BuildEntity(bool isContent, bool isMedia, BaseDto dto)
|
||||
private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto)
|
||||
{
|
||||
if (isContent)
|
||||
return BuildDocumentEntity(dto);
|
||||
if (isMedia)
|
||||
return BuildMediaEntity(dto);
|
||||
if (isMember)
|
||||
return BuildMemberEntity(dto);
|
||||
|
||||
// EntitySlim does not track changes
|
||||
var entity = new EntitySlim();
|
||||
@@ -644,6 +661,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return entity;
|
||||
}
|
||||
|
||||
private MemberEntitySlim BuildMemberEntity(BaseDto dto)
|
||||
{
|
||||
// EntitySlim does not track changes
|
||||
var entity = new MemberEntitySlim();
|
||||
BuildEntity(entity, dto);
|
||||
|
||||
entity.ContentTypeAlias = dto.Alias;
|
||||
entity.ContentTypeIcon = dto.Icon;
|
||||
entity.ContentTypeThumbnail = dto.Thumbnail;
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,17 +102,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
|
||||
var list = new List<string>
|
||||
{
|
||||
//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;
|
||||
}
|
||||
@@ -182,6 +182,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(ILanguage.IsoCode)))
|
||||
{
|
||||
//if the iso code is changing, ensure there's not another lang with the same code already assigned
|
||||
var sameCode = Sql()
|
||||
.SelectCount()
|
||||
.From<LanguageDto>()
|
||||
.Where<LanguageDto>(x => x.IsoCode == entity.IsoCode && x.Id != entity.Id);
|
||||
|
||||
var countOfSameCode = Database.ExecuteScalar<int>(sameCode);
|
||||
if (countOfSameCode > 0)
|
||||
throw new InvalidOperationException($"Cannot update the language to a new culture: {entity.IsoCode} since that culture is already assigned to another language entity.");
|
||||
}
|
||||
|
||||
// fallback cycles are detected at service level
|
||||
|
||||
// update
|
||||
@@ -250,7 +263,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));
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, 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;
|
||||
@@ -50,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<IMediaType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
|
||||
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
|
||||
// types by default on the document and media repo's so we can query by content type there too.
|
||||
// types by default on the document and media repos so we can query by content type there too.
|
||||
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>(left => left.ContentTypeId, right => right.NodeId);
|
||||
|
||||
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
@@ -546,6 +546,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (ordering.OrderBy.InvariantEquals("userName"))
|
||||
return SqlSyntax.GetFieldName<MemberDto>(x => x.LoginName);
|
||||
|
||||
if (ordering.OrderBy.InvariantEquals("updateDate"))
|
||||
return SqlSyntax.GetFieldName<ContentVersionDto>(x => x.VersionDate);
|
||||
|
||||
if (ordering.OrderBy.InvariantEquals("createDate"))
|
||||
return SqlSyntax.GetFieldName<NodeDto>(x => x.CreateDate);
|
||||
|
||||
if (ordering.OrderBy.InvariantEquals("contentTypeAlias"))
|
||||
return SqlSyntax.GetFieldName<ContentTypeDto>(x => x.Alias);
|
||||
|
||||
return base.ApplySystemOrdering(ref sql, ordering);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, 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;
|
||||
@@ -57,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<IMemberType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
public static partial class ConfigurationFieldsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a configuration field.
|
||||
/// </summary>
|
||||
/// <param name="fields">The list of configuration fields.</param>
|
||||
/// <param name="key">The key (alias) of the field.</param>
|
||||
/// <param name="name">The name (label) of the field.</param>
|
||||
/// <param name="description">The description for the field.</param>
|
||||
/// <param name="view">The path to the editor view to be used for the field.</param>
|
||||
/// <param name="config">Optional configuration used for field's editor.</param>
|
||||
public static void Add(
|
||||
this List<ConfigurationField> fields,
|
||||
string key,
|
||||
string name,
|
||||
string description,
|
||||
string view,
|
||||
IDictionary<string, object> config = null)
|
||||
{
|
||||
fields.Add(new ConfigurationField
|
||||
{
|
||||
Key = key,
|
||||
Name = name,
|
||||
Description = description,
|
||||
View = view,
|
||||
Config = config,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<DataEditorAttribute>(false);
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// Gets or sets an optional group.
|
||||
/// </summary>
|
||||
/// <remarks>The group can be used for example to group the editors by category.</remarks>
|
||||
public string Group { get; set; } = "common";
|
||||
public string Group { get; set; } = Constants.PropertyEditors.Groups.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the value editor is deprecated.
|
||||
|
||||
@@ -5,7 +5,11 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <summary>
|
||||
/// Represents a property editor for label properties.
|
||||
/// </summary>
|
||||
[DataEditor(Constants.PropertyEditors.Aliases.Label, "Label", "readonlyvalue", Icon = "icon-readonly")]
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.Label,
|
||||
"Label",
|
||||
"readonlyvalue",
|
||||
Icon = "icon-readonly")]
|
||||
public class LabelPropertyEditor : DataEditor
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <summary>
|
||||
/// Gets the type of the dynamic configuration provider.
|
||||
/// </summary>
|
||||
//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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Core
|
||||
@@ -15,12 +17,8 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Executes an action with a safe live factory
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the factory is a live factory, ensures it is refreshed and locked while executing the action.</para>
|
||||
/// </remarks>
|
||||
[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)
|
||||
@@ -37,5 +35,38 @@ namespace Umbraco.Core
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a flag to reset the ModelsBuilder models if the <see cref="IPublishedModelFactory"/> is <see cref="ILivePublishedModelFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <remarks>
|
||||
/// This does not recompile the pure live models, only sets a flag to tell models builder to recompile when they are requested.
|
||||
/// </remarks>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Umbraco.Core.Serialization
|
||||
/// <returns></returns>
|
||||
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);
|
||||
|
||||
|
||||
@@ -7,6 +7,21 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
public static class ContentTypeServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all of the element types (e.g. content types that have been marked as an element type).
|
||||
/// </summary>
|
||||
/// <param name="contentTypeService">The content type service.</param>
|
||||
/// <returns>Returns all the element types.</returns>
|
||||
public static IEnumerable<IContentType> GetAllElementTypes(this IContentTypeService contentTypeService)
|
||||
{
|
||||
if (contentTypeService == null)
|
||||
{
|
||||
return Enumerable.Empty<IContentType>();
|
||||
}
|
||||
|
||||
return contentTypeService.GetAll().Where(x => x.IsElement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the available composite content types for a given content type
|
||||
/// </summary>
|
||||
@@ -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.
|
||||
/// </param>
|
||||
/// <param name="isElement">Wether the composite content types should be applicable for an element type</param>
|
||||
/// <returns></returns>
|
||||
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<string>()
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -12,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;
|
||||
|
||||
@@ -757,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();
|
||||
@@ -886,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);
|
||||
@@ -894,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;
|
||||
}
|
||||
@@ -921,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)))
|
||||
@@ -928,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;
|
||||
}
|
||||
@@ -986,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);
|
||||
@@ -993,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1047,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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a lot of business logic cases for how the document should be persisted
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="saveEventArgs"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="raiseEvents"></param>
|
||||
/// <param name="branchOne"></param>
|
||||
/// <param name="branchRoot"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 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...
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
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<ILanguage> 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));
|
||||
@@ -1070,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;
|
||||
|
||||
@@ -1088,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
|
||||
@@ -1097,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
|
||||
{
|
||||
@@ -1162,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)
|
||||
@@ -1186,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);
|
||||
}
|
||||
|
||||
@@ -1211,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
|
||||
@@ -1236,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);
|
||||
@@ -1245,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);
|
||||
@@ -1259,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);
|
||||
@@ -1297,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;
|
||||
@@ -1325,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<ContentService>("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
|
||||
@@ -1340,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)
|
||||
@@ -1390,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<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
|
||||
yield return result;
|
||||
@@ -1416,7 +1494,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
// utility 'PublishCultures' func used by SaveAndPublishBranch
|
||||
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish)
|
||||
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish, IReadOnlyCollection<ILanguage> allLangs)
|
||||
{
|
||||
//TODO: This does not support being able to return invalid property details to bubble up to the UI
|
||||
|
||||
@@ -1426,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);
|
||||
});
|
||||
}
|
||||
@@ -1535,7 +1613,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
internal IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
|
||||
Func<IContent, HashSet<string>> shouldPublish,
|
||||
Func<IContent, HashSet<string>, bool> publishCultures,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
|
||||
int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish));
|
||||
@@ -1549,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.");
|
||||
|
||||
@@ -1557,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);
|
||||
@@ -1588,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);
|
||||
@@ -1620,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<IContent, HashSet<string>> shouldPublish,
|
||||
Func<IContent, HashSet<string>, bool> publishCultures,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
|
||||
bool isRoot,
|
||||
ICollection<IContent> publishedDocuments,
|
||||
EventMessages evtMsgs, int userId)
|
||||
EventMessages evtMsgs, int userId, IReadOnlyCollection<ILanguage> allLangs)
|
||||
{
|
||||
var culturesToPublish = shouldPublish(document);
|
||||
if (culturesToPublish == null) // null = do not include
|
||||
@@ -1636,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;
|
||||
@@ -1768,7 +1848,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
var c = _documentRepository.Get(id);
|
||||
if (c.VersionId != versionId) // don't delete the current version
|
||||
if (c.VersionId != versionId && c.PublishedVersionId != versionId) // don't delete the current or published version
|
||||
_documentRepository.DeleteVersion(versionId);
|
||||
|
||||
scope.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId));
|
||||
@@ -2343,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<ILanguage> langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture));
|
||||
private bool IsMandatoryCulture(IReadOnlyCollection<ILanguage> langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
@@ -2497,7 +2580,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <param name="culturesPublishing"></param>
|
||||
/// <param name="savingEventArgs"></param>
|
||||
/// <returns></returns>
|
||||
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing, IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
|
||||
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing,
|
||||
IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs,
|
||||
IReadOnlyCollection<ILanguage> allLangs)
|
||||
{
|
||||
// raise Publishing event
|
||||
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
|
||||
@@ -2509,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))
|
||||
@@ -2531,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);
|
||||
@@ -2676,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;
|
||||
|
||||
@@ -2894,13 +2986,27 @@ namespace Umbraco.Core.Services.Implement
|
||||
content.CreatorId = userId;
|
||||
content.WriterId = userId;
|
||||
|
||||
IEnumerable<string> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
@@ -437,7 +437,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)
|
||||
@@ -487,7 +488,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
new XElement("SortOrder", propertyType.SortOrder),
|
||||
new XElement("Mandatory", propertyType.Mandatory.ToString()),
|
||||
propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : 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);
|
||||
}
|
||||
|
||||
@@ -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<ITemplate>(template, true, evtMsgs, additionalData);
|
||||
|
||||
@@ -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<IMedia>(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);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
/// </summary>
|
||||
SuccessUnpublishMandatoryCulture = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished.
|
||||
/// </summary>
|
||||
SuccessUnpublishLastCulture = 8,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Success - Mixed
|
||||
@@ -113,9 +118,9 @@
|
||||
FailedPublishContentInvalid = FailedPublish | 8,
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird
|
||||
FailedPublishNothingToPublish = FailedPublish | 9,
|
||||
|
||||
/// <summary>
|
||||
/// The document could not be published because some mandatory cultures are missing.
|
||||
|
||||
@@ -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('/');
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
<Compile Include="CompositionExtensions_Essentials.cs" />
|
||||
<Compile Include="CompositionExtensions_FileSystems.cs" />
|
||||
<Compile Include="CompositionExtensions_Uniques.cs" />
|
||||
<Compile Include="Exceptions\PanicException.cs" />
|
||||
<Compile Include="FactoryExtensions.cs" />
|
||||
<Compile Include="Composing\RegisterFactory.cs" />
|
||||
<Compile Include="Composing\Current.cs" />
|
||||
@@ -234,6 +235,7 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\IPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\ListViewPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\MediaPickerPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\MarkdownEditorPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\NestedContentPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorCollection.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorCollectionBuilder.cs" />
|
||||
@@ -261,10 +263,13 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\FixLanguageIsoCodeLength.cs" />
|
||||
<Compile Include="Models\CultureImpact.cs" />
|
||||
<Compile Include="Models\Entities\IMediaEntitySlim.cs" />
|
||||
<Compile Include="Models\Entities\IMemberEntitySlim.cs" />
|
||||
<Compile Include="Models\Entities\MediaEntitySlim.cs" />
|
||||
<Compile Include="Models\Entities\MemberEntitySlim.cs" />
|
||||
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedContentType.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedPropertyType.cs" />
|
||||
<Compile Include="PropertyEditors\ConfigurationFieldsExtensions.cs" />
|
||||
<Compile Include="PropertyEditors\IIgnoreUserStartNodesConfig.cs" />
|
||||
<Compile Include="PublishedContentExtensions.cs" />
|
||||
<Compile Include="Models\PublishedContent\UrlMode.cs" />
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Umbraco.Examine
|
||||
if (sqlContext == null) throw new ArgumentNullException(nameof(sqlContext));
|
||||
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
|
||||
_contentValueSetBuilder = contentValueSetBuilder ?? throw new ArgumentNullException(nameof(contentValueSetBuilder));
|
||||
if (_publishedQuery != null)
|
||||
if (_publishedQuery == null)
|
||||
_publishedQuery = sqlContext.Query<IContent>().Where(x => x.Published);
|
||||
_publishedValuesOnly = publishedValuesOnly;
|
||||
_parentId = parentId;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Umbraco.Examine
|
||||
{"updateDate", new object[] {c.UpdateDate}}, //Always add invariant updateDate
|
||||
{"nodeName", (PublishedValuesOnly //Always add invariant nodeName
|
||||
? c.PublishName?.Yield()
|
||||
: c?.Name.Yield()) ?? Enumerable.Empty<string>()},
|
||||
: c.Name?.Yield()) ?? Enumerable.Empty<string>()},
|
||||
{"urlName", urlValue?.Yield() ?? Enumerable.Empty<string>()}, //Always add invariant urlName
|
||||
{"path", c.Path?.Yield() ?? Enumerable.Empty<string>()},
|
||||
{"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty<string>()},
|
||||
|
||||
@@ -12,6 +12,7 @@ using Lucene.Net.Store;
|
||||
using Umbraco.Core;
|
||||
using Version = Lucene.Net.Util.Version;
|
||||
using Umbraco.Core.Logging;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
@@ -28,6 +29,29 @@ namespace Umbraco.Examine
|
||||
/// </remarks>
|
||||
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
|
||||
|
||||
private static bool _isConfigured = false;
|
||||
private static object _configuredInit = null;
|
||||
private static object _isConfiguredLocker = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Called on startup to configure each index.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configures and unlocks all Lucene based indexes registered with the <see cref="IExamineManager"/>.
|
||||
/// </remarks>
|
||||
internal static void ConfigureIndexes(this IExamineManager examineManager, IMainDom mainDom, ILogger logger)
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(
|
||||
ref _configuredInit,
|
||||
ref _isConfigured,
|
||||
ref _isConfiguredLocker,
|
||||
() =>
|
||||
{
|
||||
examineManager.ConfigureLuceneIndexes(logger, !mainDom.IsMainDom);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
|
||||
|
||||
/// <summary>
|
||||
|
||||
12
src/Umbraco.Examine/IUmbracoIndexConfig.cs
Normal file
12
src/Umbraco.Examine/IUmbracoIndexConfig.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Examine;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
public interface IUmbracoIndexConfig
|
||||
{
|
||||
IContentValueSetValidator GetContentValueSetValidator();
|
||||
IContentValueSetValidator GetPublishedContentValueSetValidator();
|
||||
IValueSetValidator GetMemberValueSetValidator();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ using System.Threading.Tasks;
|
||||
using Examine;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Utility to rebuild all indexes ensuring minimal data queries
|
||||
/// </summary>
|
||||
|
||||
83
src/Umbraco.Examine/LuceneIndexDiagnostics.cs
Normal file
83
src/Umbraco.Examine/LuceneIndexDiagnostics.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Lucene.Net.Store;
|
||||
using Umbraco.Core.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
public class LuceneIndexDiagnostics : IIndexDiagnostics
|
||||
{
|
||||
public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger)
|
||||
{
|
||||
Index = index;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public LuceneIndex Index { get; }
|
||||
public ILogger Logger { get; }
|
||||
|
||||
public int DocumentCount
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return Index.GetIndexDocumentCount();
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<string> IsHealthy()
|
||||
{
|
||||
var isHealthy = Index.IsHealthy(out var indexError);
|
||||
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
|
||||
}
|
||||
|
||||
public virtual IReadOnlyDictionary<string, object> Metadata
|
||||
{
|
||||
get
|
||||
{
|
||||
var luceneDir = Index.GetLuceneDirectory();
|
||||
var d = new Dictionary<string, object>
|
||||
{
|
||||
[nameof(UmbracoExamineIndex.CommitCount)] = Index.CommitCount,
|
||||
[nameof(UmbracoExamineIndex.DefaultAnalyzer)] = Index.DefaultAnalyzer.GetType().Name,
|
||||
["LuceneDirectory"] = luceneDir.GetType().Name
|
||||
};
|
||||
|
||||
if (luceneDir is FSDirectory fsDir)
|
||||
{
|
||||
d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/');
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -64,14 +64,17 @@
|
||||
<Compile Include="ExamineExtensions.cs" />
|
||||
<Compile Include="IContentValueSetBuilder.cs" />
|
||||
<Compile Include="IContentValueSetValidator.cs" />
|
||||
<Compile Include="IUmbracoIndexConfig.cs" />
|
||||
<Compile Include="IIndexCreator.cs" />
|
||||
<Compile Include="IIndexDiagnostics.cs" />
|
||||
<Compile Include="IIndexPopulator.cs" />
|
||||
<Compile Include="UmbracoIndexConfig.cs" />
|
||||
<Compile Include="IndexPopulator.cs" />
|
||||
<Compile Include="IndexRebuilder.cs" />
|
||||
<Compile Include="IPublishedContentValueSetBuilder.cs" />
|
||||
<Compile Include="IUmbracoIndex.cs" />
|
||||
<Compile Include="IValueSetBuilder.cs" />
|
||||
<Compile Include="LuceneIndexDiagnostics.cs" />
|
||||
<Compile Include="MediaIndexPopulator.cs" />
|
||||
<Compile Include="MediaValueSetBuilder.cs" />
|
||||
<Compile Include="MemberIndexPopulator.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<string, object> 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<string> IsHealthy()
|
||||
{
|
||||
var isHealthy = _index.IsHealthy(out var indexError);
|
||||
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
|
||||
}
|
||||
|
||||
public virtual IReadOnlyDictionary<string, object> Metadata
|
||||
{
|
||||
get
|
||||
{
|
||||
var d = new Dictionary<string, object>
|
||||
{
|
||||
[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)
|
||||
{
|
||||
|
||||
34
src/Umbraco.Examine/UmbracoIndexConfig.cs
Normal file
34
src/Umbraco.Examine/UmbracoIndexConfig.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Examine;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
public class UmbracoIndexConfig : IUmbracoIndexConfig
|
||||
{
|
||||
public UmbracoIndexConfig(IPublicAccessService publicAccessService)
|
||||
{
|
||||
PublicAccessService = publicAccessService;
|
||||
}
|
||||
|
||||
protected IPublicAccessService PublicAccessService { get; }
|
||||
public IContentValueSetValidator GetContentValueSetValidator()
|
||||
{
|
||||
return new ContentValueSetValidator(false, true, PublicAccessService);
|
||||
}
|
||||
|
||||
public IContentValueSetValidator GetPublishedContentValueSetValidator()
|
||||
{
|
||||
return new ContentValueSetValidator(true, false, PublicAccessService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="IValueSetValidator"/> for the member indexer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IValueSetValidator GetMemberValueSetValidator()
|
||||
{
|
||||
return new MemberValueSetValidator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<MediaCacheRefresher.JsonPayload[]>(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<ContentCacheRefresher.JsonPayload[]>(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);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Tests.Composing
|
||||
@@ -35,7 +36,7 @@ namespace Umbraco.Tests.Composing
|
||||
.Returns(() => factoryFactory?.Invoke(mockedFactory));
|
||||
|
||||
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
|
||||
var typeLoader = new TypeLoader(Mock.Of<IAppPolicyCache>(), "", logger);
|
||||
var typeLoader = new TypeLoader(Mock.Of<IAppPolicyCache>(), IOHelper.MapPath("~/App_Data/TEMP"), logger);
|
||||
var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of<IRuntimeState>());
|
||||
|
||||
// create the factory, ensure it is the mocked factory
|
||||
|
||||
@@ -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<IGlobalSettings>()); //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<IGlobalSettings>()); //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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
@@ -1087,7 +1087,7 @@ namespace Umbraco.Tests.Integration
|
||||
[Test]
|
||||
public void EmptyRecycleBinContent()
|
||||
{
|
||||
ServiceContext.ContentService.EmptyRecycleBin();
|
||||
ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId);
|
||||
|
||||
var content = CreateContent();
|
||||
Assert.IsNotNull(content);
|
||||
@@ -1095,7 +1095,7 @@ namespace Umbraco.Tests.Integration
|
||||
ServiceContext.ContentService.MoveToRecycleBin(content);
|
||||
|
||||
ResetEvents();
|
||||
ServiceContext.ContentService.EmptyRecycleBin();
|
||||
ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId);
|
||||
|
||||
Assert.AreEqual(2, _msgCount);
|
||||
Assert.AreEqual(2, _events.Count);
|
||||
@@ -1109,7 +1109,7 @@ namespace Umbraco.Tests.Integration
|
||||
[Test]
|
||||
public void EmptyRecycleBinContents()
|
||||
{
|
||||
ServiceContext.ContentService.EmptyRecycleBin();
|
||||
ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId);
|
||||
|
||||
var content1 = CreateContent();
|
||||
Assert.IsNotNull(content1);
|
||||
@@ -1120,7 +1120,7 @@ namespace Umbraco.Tests.Integration
|
||||
ServiceContext.ContentService.MoveToRecycleBin(content2);
|
||||
|
||||
ResetEvents();
|
||||
ServiceContext.ContentService.EmptyRecycleBin();
|
||||
ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId);
|
||||
|
||||
Assert.AreEqual(3, _msgCount);
|
||||
Assert.AreEqual(4, _events.Count);
|
||||
@@ -1136,7 +1136,7 @@ namespace Umbraco.Tests.Integration
|
||||
[Test]
|
||||
public void EmptyRecycleBinBranch()
|
||||
{
|
||||
ServiceContext.ContentService.EmptyRecycleBin();
|
||||
ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId);
|
||||
|
||||
var content1 = CreateBranch();
|
||||
Assert.IsNotNull(content1);
|
||||
@@ -1151,7 +1151,7 @@ namespace Umbraco.Tests.Integration
|
||||
var content4C = Children(content1C[2]).ToArray();
|
||||
var content5C = Children(content1C[3]).ToArray();
|
||||
|
||||
ServiceContext.ContentService.EmptyRecycleBin();
|
||||
ServiceContext.ContentService.EmptyRecycleBin(Constants.Security.SuperUserId);
|
||||
|
||||
Assert.AreEqual(14, _msgCount);
|
||||
Assert.AreEqual(14, _events.Count);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
/// <summary>
|
||||
/// Implements a published snapshot service.
|
||||
/// </summary>
|
||||
internal class PublishedSnapshotService : PublishedSnapshotServiceBase
|
||||
internal class XmlPublishedSnapshotService : PublishedSnapshotServiceBase
|
||||
{
|
||||
private readonly XmlStore _xmlStore;
|
||||
private readonly RoutesCache _routesCache;
|
||||
@@ -41,7 +41,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
#region Constructors
|
||||
|
||||
// used in WebBootManager + tests
|
||||
public PublishedSnapshotService(ServiceContext serviceContext,
|
||||
public XmlPublishedSnapshotService(ServiceContext serviceContext,
|
||||
IPublishedContentTypeFactory publishedContentTypeFactory,
|
||||
IScopeProvider scopeProvider,
|
||||
IAppCache requestCache,
|
||||
@@ -65,7 +65,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
}
|
||||
|
||||
// used in some tests
|
||||
internal PublishedSnapshotService(ServiceContext serviceContext,
|
||||
internal XmlPublishedSnapshotService(ServiceContext serviceContext,
|
||||
IPublishedContentTypeFactory publishedContentTypeFactory,
|
||||
IScopeProvider scopeProvider,
|
||||
IAppCache requestCache,
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
/// Represents the Xml storage for the Xml published cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>One instance of <see cref="XmlStore"/> is instantiated by the <see cref="PublishedSnapshotService"/> and
|
||||
/// <para>One instance of <see cref="XmlStore"/> is instantiated by the <see cref="XmlPublishedSnapshotService"/> and
|
||||
/// then passed to all <see cref="PublishedContentCache"/> instances that are created (one per request).</para>
|
||||
/// <para>This class should *not* be public.</para>
|
||||
/// </remarks>
|
||||
|
||||
@@ -70,50 +70,128 @@ namespace Umbraco.Tests.Models
|
||||
[Test]
|
||||
public void ValidateVariationTests()
|
||||
{
|
||||
void Assert4A(ContentVariation v, string c, string s, bool xx)
|
||||
{
|
||||
Assert4B(v, c, s, xx, xx, xx, xx);
|
||||
}
|
||||
|
||||
void Assert4B(ContentVariation v, string c, string s, bool ew, bool nn, bool en, bool nw)
|
||||
{
|
||||
Assert.AreEqual(ew, v.ValidateVariation(c, s, true, true, false));
|
||||
Assert.AreEqual(nn, v.ValidateVariation(c, s, false, false, false));
|
||||
Assert.AreEqual(en, v.ValidateVariation(c, s, true, false, false));
|
||||
Assert.AreEqual(nw, v.ValidateVariation(c, s, false, true, false));
|
||||
}
|
||||
// All tests:
|
||||
// 1. if exact is set to true: culture cannot be null when the ContentVariation.Culture flag is set
|
||||
// 2. if wildcards is set to false: fail when "*" is passed in as either culture or segment.
|
||||
// 3. ContentVariation flag is ignored when wildcards are used.
|
||||
// 4. Empty string is considered the same as null
|
||||
|
||||
#region Nothing
|
||||
|
||||
// always support invariant,neutral
|
||||
Assert4A(ContentVariation.Nothing, null, null, true);
|
||||
|
||||
// never support culture and/or segment
|
||||
Assert4A(ContentVariation.Nothing, "culture", null, false);
|
||||
Assert4A(ContentVariation.Nothing, null, "", true);
|
||||
Assert4B(ContentVariation.Nothing, null, "*", true, false, false, true);
|
||||
Assert4A(ContentVariation.Nothing, null, "segment", false);
|
||||
Assert4A(ContentVariation.Nothing, "", null, true);
|
||||
Assert4A(ContentVariation.Nothing, "", "", true);
|
||||
Assert4B(ContentVariation.Nothing, "", "*", true, false, false, true);
|
||||
Assert4A(ContentVariation.Nothing, "", "segment", false);
|
||||
Assert4B(ContentVariation.Nothing, "*", null, true, false, false, true);
|
||||
Assert4B(ContentVariation.Nothing, "*", "", true, false, false, true);
|
||||
Assert4B(ContentVariation.Nothing, "*", "*", true, false, false, true);
|
||||
Assert4A(ContentVariation.Nothing, "*", "segment", false);
|
||||
Assert4A(ContentVariation.Nothing, "culture", null, false);
|
||||
Assert4A(ContentVariation.Nothing, "culture", "", false);
|
||||
Assert4A(ContentVariation.Nothing, "culture", "*", false);
|
||||
Assert4A(ContentVariation.Nothing, "culture", "segment", false);
|
||||
|
||||
// support '*' only when wildcards are supported
|
||||
Assert4B(ContentVariation.Nothing, "*", null, true, false, false, true);
|
||||
Assert4B(ContentVariation.Nothing, null, "*", true, false, false, true);
|
||||
Assert4B(ContentVariation.Nothing, "*", "*", true, false, false, true);
|
||||
#endregion
|
||||
|
||||
#region Culture
|
||||
|
||||
// support invariant if not exact
|
||||
Assert4B(ContentVariation.Culture, null, null, false, true, false, true);
|
||||
|
||||
// support invariant if not exact, '*' when wildcards are supported
|
||||
Assert4B(ContentVariation.Culture, "*", null, true, false, false, true);
|
||||
Assert4B(ContentVariation.Culture, null, "", false, true, false, true);
|
||||
Assert4B(ContentVariation.Culture, null, "*", false, false, false, true);
|
||||
Assert4B(ContentVariation.Culture, "*", "*", true, false, false, true);
|
||||
|
||||
// never support segment
|
||||
Assert4A(ContentVariation.Culture, null, "segment", false);
|
||||
Assert4A(ContentVariation.Culture, "culture", "segment", false);
|
||||
Assert4B(ContentVariation.Culture, "", null, false, true, false, true);
|
||||
Assert4B(ContentVariation.Culture, "", "", false, true, false, true);
|
||||
Assert4B(ContentVariation.Culture, "", "*", false, false, false, true);
|
||||
Assert4A(ContentVariation.Culture, "", "segment", false);
|
||||
Assert4B(ContentVariation.Culture, "*", null, true, false, false, true);
|
||||
Assert4B(ContentVariation.Culture, "*", "", true, false, false, true);
|
||||
Assert4B(ContentVariation.Culture, "*", "*", true, false, false, true);
|
||||
Assert4A(ContentVariation.Culture, "*", "segment", false);
|
||||
|
||||
Assert4B(ContentVariation.Culture, null, "*", false, false, false, true);
|
||||
Assert4A(ContentVariation.Culture, "culture", null, true);
|
||||
Assert4A(ContentVariation.Culture, "culture", "", true);
|
||||
Assert4B(ContentVariation.Culture, "culture", "*", true, false, false, true);
|
||||
Assert4A(ContentVariation.Culture, "culture", "segment", false);
|
||||
|
||||
// could do the same with .Segment, and .CultureAndSegment
|
||||
#endregion
|
||||
|
||||
#region Segment
|
||||
|
||||
Assert4B(ContentVariation.Segment, null, null, true, true, true, true);
|
||||
Assert4B(ContentVariation.Segment, null, "", true, true, true, true);
|
||||
Assert4B(ContentVariation.Segment, null, "*", true, false, false, true);
|
||||
Assert4A(ContentVariation.Segment, null, "segment", true);
|
||||
Assert4B(ContentVariation.Segment, "", null, true, true, true, true);
|
||||
Assert4B(ContentVariation.Segment, "", "", true, true, true, true);
|
||||
Assert4B(ContentVariation.Segment, "", "*", true, false, false, true);
|
||||
Assert4A(ContentVariation.Segment, "", "segment", true);
|
||||
Assert4B(ContentVariation.Segment, "*", null, true, false, false, true);
|
||||
Assert4B(ContentVariation.Segment, "*", "", true, false, false, true);
|
||||
Assert4B(ContentVariation.Segment, "*", "*", true, false, false, true);
|
||||
Assert4B(ContentVariation.Segment, "*", "segment", true, false, false, true);
|
||||
Assert4A(ContentVariation.Segment, "culture", null, false);
|
||||
Assert4A(ContentVariation.Segment, "culture", "", false);
|
||||
Assert4A(ContentVariation.Segment, "culture", "*", false);
|
||||
Assert4A(ContentVariation.Segment, "culture", "segment", false);
|
||||
|
||||
#endregion
|
||||
|
||||
#region CultureAndSegment
|
||||
|
||||
Assert4B(ContentVariation.CultureAndSegment, null, null, false, true, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, null, "", false, true, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, null, "*", false, false, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, null, "segment", false, true, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "", null, false, true, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "", "", false, true, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "", "*", false, false, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "", "segment", false, true, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "*", null, true, false, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "*", "", true, false, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "*", "*", true, false, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "*", "segment", true, false, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "culture", null, true, true, true, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "culture", "", true, true, true, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "culture", "*", true, false, false, true);
|
||||
Assert4B(ContentVariation.CultureAndSegment, "culture", "segment", true, true, true, true);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts the result of <see cref="ContentVariationExtensions.ValidateVariation(ContentVariation, string, string, bool, bool, bool)"/>
|
||||
/// </summary>
|
||||
/// <param name="variation"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="segment"></param>
|
||||
/// <param name="exactAndWildcards">Validate using Exact + Wildcards flags</param>
|
||||
/// <param name="nonExactAndNoWildcards">Validate using non Exact + no Wildcard flags</param>
|
||||
/// <param name="exactAndNoWildcards">Validate using Exact + no Wildcard flags</param>
|
||||
/// <param name="nonExactAndWildcards">Validate using non Exact + Wildcard flags</param>
|
||||
private static void Assert4B(ContentVariation variation, string culture, string segment,
|
||||
bool exactAndWildcards, bool nonExactAndNoWildcards, bool exactAndNoWildcards, bool nonExactAndWildcards)
|
||||
{
|
||||
Assert.AreEqual(exactAndWildcards, variation.ValidateVariation(culture, segment, true, true, false));
|
||||
Assert.AreEqual(nonExactAndNoWildcards, variation.ValidateVariation(culture, segment, false, false, false));
|
||||
Assert.AreEqual(exactAndNoWildcards, variation.ValidateVariation(culture, segment, true, false, false));
|
||||
Assert.AreEqual(nonExactAndWildcards, variation.ValidateVariation(culture, segment, false, true, false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts the result of <see cref="ContentVariationExtensions.ValidateVariation(ContentVariation, string, string, bool, bool, bool)"/>
|
||||
/// where expectedResult matches all combinations of Exact + Wildcard
|
||||
/// </summary>
|
||||
/// <param name="variation"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="segment"></param>
|
||||
/// <param name="expectedResult"></param>
|
||||
private static void Assert4A(ContentVariation variation, string culture, string segment, bool expectedResult)
|
||||
{
|
||||
Assert4B(variation, culture, segment, expectedResult, expectedResult, expectedResult, expectedResult);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -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<PropertyEditorCollection>());
|
||||
Factory.GetInstance<PropertyEditorCollection>(),
|
||||
Factory.GetInstance<IScopeProvider>());
|
||||
|
||||
private IPackageInstallation PackageInstallation => new PackageInstallation(
|
||||
PackageDataInstallation,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user