diff --git a/.editorconfig b/.editorconfig
index 80eabec233..29e21d01ed 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -27,4 +27,10 @@ dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.prefix_underscore.capitalization = camel_case
-dotnet_naming_style.prefix_underscore.required_prefix = _
\ No newline at end of file
+dotnet_naming_style.prefix_underscore.required_prefix = _
+
+# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md
+[*.cs]
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
\ No newline at end of file
diff --git a/docs/BUILD.md b/.github/BUILD.md
similarity index 100%
rename from docs/BUILD.md
rename to .github/BUILD.md
diff --git a/docs/CLEAR.md b/.github/CLEAR.md
similarity index 100%
rename from docs/CLEAR.md
rename to .github/CLEAR.md
diff --git a/docs/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
similarity index 100%
rename from docs/CODE_OF_CONDUCT.md
rename to .github/CODE_OF_CONDUCT.md
diff --git a/docs/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 100%
rename from docs/CONTRIBUTING.md
rename to .github/CONTRIBUTING.md
diff --git a/docs/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md
similarity index 96%
rename from docs/CONTRIBUTING_DETAILED.md
rename to .github/CONTRIBUTING_DETAILED.md
index bffac4801b..020346dc5e 100644
--- a/docs/CONTRIBUTING_DETAILED.md
+++ b/.github/CONTRIBUTING_DETAILED.md
@@ -129,7 +129,7 @@ We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-fl
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
-Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
+Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 (version 15.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.

@@ -156,4 +156,4 @@ git rebase upstream/dev-v7
In this command we're syncing with the `dev-v7` branch, but you can of course choose another one if needed.
-(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
\ No newline at end of file
+(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
diff --git a/docs/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md
similarity index 100%
rename from docs/CONTRIBUTION_GUIDELINES.md
rename to .github/CONTRIBUTION_GUIDELINES.md
diff --git a/.github/ISSUE_TEMPLATE/1_Bug.md b/.github/ISSUE_TEMPLATE/1_Bug.md
new file mode 100644
index 0000000000..a1e33e3854
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/1_Bug.md
@@ -0,0 +1,66 @@
+---
+name: 🐛 Bug Report
+about: File a bug report, if you've discovered a problem in Umbraco.
+---
+
+A brief description of the issue goes here.
+
+
+
+
+
+Reproduction
+------------
+
+If you're filing a bug, please describe how to reproduce it. Include as much
+relevant information as possible, such as:
+
+### Bug summary
+
+
+
+### Specifics
+
+
+
+### Steps to reproduce
+
+
+
+### Expected result
+
+
+
+### Actual result
+
+
diff --git a/.github/ISSUE_TEMPLATE/2_Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md
new file mode 100644
index 0000000000..16ec2568dd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/2_Feature_request.md
@@ -0,0 +1,31 @@
+---
+name: 📮 Feature Request
+about: Open a feature request, if you want to propose a new feature.
+---
+
+A brief description of your feature request goes here.
+
+
+
+
+How can you help?
+-------------------------------
+
+
diff --git a/.github/ISSUE_TEMPLATE/3_Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md
new file mode 100644
index 0000000000..829df982f9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/3_Support_question.md
@@ -0,0 +1,9 @@
+---
+name: ⁉️ Support Question
+about: Having trouble with Umbraco? -> https://our.umbraco.com
+---
+
+This issue tracker is NOT meant for support questions. If you have a question,
+please join us on the forum at https://our.umbraco.com.
+
+Thanks!
diff --git a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md
new file mode 100644
index 0000000000..8893647aa8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md
@@ -0,0 +1,9 @@
+---
+name: 📖 Documentation Issue
+about: See https://github.com/umbraco/UmbracoDocs/issues for documentation issues
+---
+
+The Umbraco documentation has its own dedicated repository. Please open your
+documentation-related issue at https://github.com/umbraco/UmbracoDocs/issues
+
+Thanks!
diff --git a/.github/ISSUE_TEMPLATE/5_Security_issue.md b/.github/ISSUE_TEMPLATE/5_Security_issue.md
new file mode 100644
index 0000000000..84c5f5989c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/5_Security_issue.md
@@ -0,0 +1,31 @@
+---
+name: 🔐 Security Issue
+about: Discovered a Security Issue in Umbraco?
+---
+
+⚠️ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW.
+
+If you have found a security issue in Umbraco, please send the details to
+security@umbraco.com and don't disclose it publicly until we can provide a fix for
+it. If you wish, we'll credit you for finding verified issues, when we release
+the patched version.
+
+❗ Please read more about how to report security issues on https://umbraco.com/security
+
+A note on "Self XSS"
+--------------------
+
+Umbraco is a CMS, that allows users to edit content on a website. As such,
+all _authenticated users_ can:
+
+ - Edit content, and (depending on the field types) insert HTML and CSS in that
+ content, with a variety of allowed attributes.
+ - Depending on the user level: Edit template files, and insert C#, HTML, CSS and
+ javascript in so on.
+ - Upload files to the site, which will become publicly available.
+
+We see these functionalities as _features_, and not as security issues. Please
+report the mentioned items only if they can be performed by non-authorized
+users, or other exploitable vulnerabilities.
+
+Thanks!
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..6275d161dc
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,11 @@
+### Prerequisites
+
+- [ ] I have [created an issue](https://github.com/umbraco/Umbraco-CMS/issues) for the proposed changes in this PR, the link is:
+- [ ] I have added steps to test this contribution in the description below
+
+### Description
+
+
+
+
+
diff --git a/docs/README.md b/.github/README.md
similarity index 91%
rename from docs/README.md
rename to .github/README.md
index 5316ad0604..83de27c859 100644
--- a/docs/README.md
+++ b/.github/README.md
@@ -48,4 +48,5 @@ Umbraco is contribution focused and community driven. If you want to contribute
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](CONTRIBUTING_DETAILED.md#reporting-bugs).
-To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org).
+You can comment and report issues on the [github issue tracker](https://github.com/umbraco/Umbraco-CMS/issues).
+Since [September 2018](https://umbraco.com/blog/a-second-take-on-umbraco-issue-tracker-hello-github-issues/) the old issue tracker is in read only mode, but can still be found at [http://issues.umbraco.org](http://issues.umbraco.org).
diff --git a/docs/REVIEW_PROCESS.md b/.github/REVIEW_PROCESS.md
similarity index 100%
rename from docs/REVIEW_PROCESS.md
rename to .github/REVIEW_PROCESS.md
diff --git a/docs/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md
similarity index 100%
rename from docs/V8_GETTING_STARTED.md
rename to .github/V8_GETTING_STARTED.md
diff --git a/docs/img/clonefork.png b/.github/img/clonefork.png
similarity index 100%
rename from docs/img/clonefork.png
rename to .github/img/clonefork.png
diff --git a/docs/img/createpullrequest.png b/.github/img/createpullrequest.png
similarity index 100%
rename from docs/img/createpullrequest.png
rename to .github/img/createpullrequest.png
diff --git a/docs/img/defaultbranch.png b/.github/img/defaultbranch.png
similarity index 100%
rename from docs/img/defaultbranch.png
rename to .github/img/defaultbranch.png
diff --git a/docs/img/forkrepository.png b/.github/img/forkrepository.png
similarity index 100%
rename from docs/img/forkrepository.png
rename to .github/img/forkrepository.png
diff --git a/docs/img/gulpbuild.png b/.github/img/gulpbuild.png
similarity index 100%
rename from docs/img/gulpbuild.png
rename to .github/img/gulpbuild.png
diff --git a/docs/img/vimeo.png b/.github/img/vimeo.png
similarity index 100%
rename from docs/img/vimeo.png
rename to .github/img/vimeo.png
diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index b142e08c5f..daa0018668 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -1,92 +1,56 @@
-
- UmbracoCms.Core
- 8.0.0
- Umbraco Cms Core Binaries
- Umbraco HQ
- Umbraco HQ
- http://opensource.org/licenses/MIT
- http://umbraco.com/
- http://umbraco.com/media/357769/100px_transparent.png
- false
- Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project.
- Contains the core assemblies needed to run Umbraco Cms
- en-US
- umbraco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ UmbracoCms.Core
+ 8.0.0
+ Umbraco Cms Core Binaries
+ Umbraco HQ
+ Umbraco HQ
+ http://opensource.org/licenses/MIT
+ http://umbraco.com/
+ http://umbraco.com/media/357769/100px_transparent.png
+ false
+ Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET project.
+ Contains the core assemblies needed to run Umbraco Cms
+ en-US
+ umbraco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
new file mode 100644
index 0000000000..e9bd8ca6ea
--- /dev/null
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -0,0 +1,62 @@
+
+
+
+ UmbracoCms.Web
+ 8.0.0
+ Umbraco Cms Core Binaries
+ Umbraco HQ
+ Umbraco HQ
+ http://opensource.org/licenses/MIT
+ http://umbraco.com/
+ http://umbraco.com/media/357769/100px_transparent.png
+ false
+ Contains the web assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET project.
+ Contains the core assemblies needed to run Umbraco Cms
+ en-US
+ umbraco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index 1deec235f8..434fe812ef 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -1,58 +1,65 @@
-
- UmbracoCms
- 8.0.0
- Umbraco Cms
- Umbraco HQ
- Umbraco HQ
- http://opensource.org/licenses/MIT
- http://umbraco.com/
- http://umbraco.com/media/357769/100px_transparent.png
- false
- Installs Umbraco Cms in your Visual Studio ASP.NET project
- Installs Umbraco Cms in your Visual Studio ASP.NET project
- en-US
- umbraco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ UmbracoCms
+ 8.0.0
+ Umbraco Cms
+ Umbraco HQ
+ Umbraco HQ
+ http://opensource.org/licenses/MIT
+ http://umbraco.com/
+ http://umbraco.com/media/357769/100px_transparent.png
+ false
+ Installs Umbraco Cms in your Visual Studio ASP.NET project
+ Installs Umbraco Cms in your Visual Studio ASP.NET project
+ en-US
+ umbraco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets
index fde5b4ea81..ac0e9e976d 100644
--- a/build/NuSpecs/build/UmbracoCms.targets
+++ b/build/NuSpecs/build/UmbracoCms.targets
@@ -47,9 +47,6 @@
umbraco
-
- umbraco_client
-
.
diff --git a/build/NuSpecs/tools/Readme.txt b/build/NuSpecs/tools/Readme.txt
index bcac64979d..53915436ac 100644
--- a/build/NuSpecs/tools/Readme.txt
+++ b/build/NuSpecs/tools/Readme.txt
@@ -1,26 +1,26 @@
- _ _ __ __ ____ _____ _____ ____
- | | | | \/ | _ \| __ \ /\ / ____/ __ \
+ _ _ __ __ ____ _____ _____ ____
+ | | | | \/ | _ \| __ \ /\ / ____/ __ \
| | | | \ / | |_) | |__) | / \ | | | | | |
| | | | |\/| | _ <| _ / / /\ \| | | | | |
| |__| | | | | |_) | | \ \ / ____ | |___| |__| |
- \____/|_| |_|____/|_| \_/_/ \_\_____\____/
-
+ \____/|_| |_|____/|_| \_/_/ \_\_____\____/
+
----------------------------------------------------
Don't forget to build!
-When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config
-file (and config files in the config folder).
+When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config
+file (and config files in the config folder).
-This NuGet package includes build targets that extend the creation of a deploy package, which is generated by
+This NuGet package includes build targets that extend the creation of a deploy package, which is generated by
Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use
Publish this won't affect you.
-The following items will now be automatically included when creating a deploy package or publishing to the file
-system: umbraco, umbraco_client, config\splashes and global.asax.
+The following items will now be automatically included when creating a deploy package or publishing to the file
+system: umbraco, config\splashes and global.asax.
Please read the release notes on our.umbraco.com:
-http://our.umbraco.com/contribute/releases
+https://our.umbraco.com/download/releases
-- Umbraco
\ No newline at end of file
+- Umbraco
diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt
index ff5c80b1c2..df364c64ed 100644
--- a/build/NuSpecs/tools/ReadmeUpgrade.txt
+++ b/build/NuSpecs/tools/ReadmeUpgrade.txt
@@ -1,39 +1,30 @@
- _ _ __ __ ____ _____ _____ ____
- | | | | \/ | _ \| __ \ /\ / ____/ __ \
+ _ _ __ __ ____ _____ _____ ____
+ | | | | \/ | _ \| __ \ /\ / ____/ __ \
| | | | \ / | |_) | |__) | / \ | | | | | |
| | | | |\/| | _ <| _ / / /\ \| | | | | |
| |__| | | | | |_) | | \ \ / ____ | |___| |__| |
- \____/|_| |_|____/|_| \_/_/ \_\_____\____/
-
+ \____/|_| |_|____/|_| \_/_/ \_\_____\____/
+
----------------------------------------------------
-*** IMPORTANT NOTICE FOR UPGRADES FROM VERSIONS BELOW 7.7.0 ***
-
-Be sure to read the version specific upgrade information before proceeding:
-https://our.umbraco.com/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-7-0
-
-Depending on the version you are upgrading from, you may need to make some changes to your web.config
-and you will need to be aware of the breaking changes listed there to see if these affect your installation.
-
-
Don't forget to build!
We've done our best to transform your configuration files but in case something is not quite right: remember we
backed up your files in App_Data\NuGetBackup so you can find the original files before they were transformed.
-We've overwritten all the files in the Umbraco and Umbraco_Client folder, these have been backed up in
-App_Data\NuGetBackup. We didn't overwrite the UI.xml file nor did we remove any files or folders that you or
-a package might have added. Only the existing files were overwritten. If you customized anything then make
+We've overwritten all the files in the Umbraco folder, these have been backed up in
+App_Data\NuGetBackup. We didn't overwrite the UI.xml file nor did we remove any files or folders that you or
+a package might have added. Only the existing files were overwritten. If you customized anything then make
sure to do a compare and merge with the NuGetBackup folder.
-This NuGet package includes build targets that extend the creation of a deploy package, which is generated by
+This NuGet package includes build targets that extend the creation of a deploy package, which is generated by
Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use
Publish this won't affect you.
-The following items will now be automatically included when creating a deploy package or publishing to the file
-system: umbraco, umbraco_client, config\splashes and global.asax.
+The following items will now be automatically included when creating a deploy package or publishing to the file
+system: umbraco, config\splashes and global.asax.
Please read the release notes on our.umbraco.com:
http://our.umbraco.com/contribute/releases
-- Umbraco
\ No newline at end of file
+- Umbraco
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index 027d1e49a6..fa3ab86ba2 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -150,10 +150,6 @@
-
-
-
-
diff --git a/build/NuSpecs/tools/applications.config.install.xdt b/build/NuSpecs/tools/applications.config.install.xdt
index f4a0060150..5c05b2b4e9 100644
--- a/build/NuSpecs/tools/applications.config.install.xdt
+++ b/build/NuSpecs/tools/applications.config.install.xdt
@@ -1,11 +1,11 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1
index 684739072c..0d4bb20b48 100644
--- a/build/NuSpecs/tools/install.ps1
+++ b/build/NuSpecs/tools/install.ps1
@@ -44,14 +44,6 @@ if ($project) {
robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log
robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log
- $umbracoClientFolder = Join-Path $projectPath "Umbraco_Client"
- New-Item -ItemType Directory -Force -Path $umbracoClientFolder
- $umbracoClientFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco_Client"
- $umbracoClientBackupPath = Join-Path $backupPath "Umbraco_Client"
- New-Item -ItemType Directory -Force -Path $umbracoClientBackupPath
- robocopy $umbracoClientFolder $umbracoClientBackupPath /e /LOG:$copyLogsPath\UmbracoClientBackup.log
- robocopy $umbracoClientFolderSource $umbracoClientFolder /is /it /e /LOG:$copyLogsPath\UmbracoClientCopy.log
-
$copyWebconfig = $true
$destinationWebConfig = Join-Path $projectPath "Web.config"
@@ -85,15 +77,7 @@ if ($project) {
$splashesDestination = Join-Path $projectPath "Config\splashes\"
New-Item $splashesDestination -Type directory
Copy-Item $splashesSource $splashesDestination -Force
-
- $sqlCe64Source = Join-Path $installPath "UmbracoFiles\bin\amd64\*"
- $sqlCe64Destination = Join-Path $projectPath "bin\amd64\"
- Copy-Item $sqlCe64Source $sqlCe64Destination -Force
-
- $sqlCex86Source = Join-Path $installPath "UmbracoFiles\bin\x86\*"
- $sqlCex86Destination = Join-Path $projectPath "bin\x86\"
- Copy-Item $sqlCex86source $sqlCex86Destination -Force
-
+
$umbracoUIXMLSource = Join-Path $installPath "UmbracoFiles\Umbraco\Config\Create\UI.xml"
$umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml"
Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force
@@ -159,4 +143,4 @@ if ($project) {
{
$DTE.ItemOperations.OpenFile($toolsPath + '\ReadmeUpgrade.txt')
}
-}
\ No newline at end of file
+}
diff --git a/build/build.ps1 b/build/build.ps1
index 964c29fb63..c7deb59f1e 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -69,6 +69,9 @@
$global:node_nodepath = $this.ClearEnvVar("NODEPATH")
$global:node_npmcache = $this.ClearEnvVar("NPM_CONFIG_CACHE")
$global:node_npmprefix = $this.ClearEnvVar("NPM_CONFIG_PREFIX")
+
+ # https://github.com/gruntjs/grunt-contrib-connect/issues/235
+ $this.SetEnvVar("NODE_NO_HTTP2", "1")
})
$ubuild.DefineMethod("RestoreNode",
@@ -78,6 +81,8 @@
$this.SetEnvVar("NODEPATH", $node_nodepath)
$this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache)
$this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix)
+
+ $ignore = $this.ClearEnvVar("NODE_NO_HTTP2")
})
$ubuild.DefineMethod("CompileBelle",
@@ -124,7 +129,7 @@
Write-Output "### install gulp-cli" >> $log 2>&1
&npm install -g gulp-cli --quiet >> $log 2>&1
- if (-not $?) { throw "Failed to install gulp-cli" } # that one is expected to work
+ $error.Clear() # that one fails 'cos some files not being removed - ignore
Write-Output "### gulp build for version $($this.Version.Release)" >> $log 2>&1
&gulp build --buildversion=$this.Version.Release >> $log 2>&1
@@ -399,9 +404,15 @@
-Symbols -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log"
if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." }
+ &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" `
+ -Properties BuildTmp="$($this.BuildTemp)" `
+ -Version "$($this.Version.Semver.ToString())" `
+ -Symbols -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log"
+ if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." }
+
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" `
-Properties BuildTmp="$($this.BuildTemp)" `
- -Version $this.Version.Semver.ToString() `
+ -Version "$($this.Version.Semver.ToString())" `
-Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cms.log"
if (-not $?) { throw "Failed to pack NuGet UmbracoCms." }
@@ -417,7 +428,7 @@
$ubuild.DefineMethod("VerifyNuGet",
{
$this.VerifyNuGetConsistency(
- ("UmbracoCms", "UmbracoCms.Core"),
+ ("UmbracoCms", "UmbracoCms.Core", "UmbracoCms.Web"),
("Umbraco.Core", "Umbraco.Web", "Umbraco.Web.UI", "Umbraco.Examine"))
if ($this.OnError()) { return }
})
@@ -427,7 +438,7 @@
Write-Host "Prepare Azure Gallery"
$this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput)
})
-
+
$ubuild.DefineMethod("Build",
{
$error.Clear()
@@ -457,6 +468,7 @@
if ($this.OnError()) { return }
$this.PrepareAzureGallery()
if ($this.OnError()) { return }
+ Write-Host "Done"
})
# ################################################################
@@ -472,5 +484,4 @@
$ubuild.Build()
if ($ubuild.OnError()) { return }
}
- Write-Host "Done"
if ($get) { return $ubuild }
diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md
deleted file mode 100644
index db1e5c88bd..0000000000
--- a/docs/PULL_REQUEST_TEMPLATE.md
+++ /dev/null
@@ -1,11 +0,0 @@
-### Prerequisites
-
-- [ ] I have written a descriptive pull-request title
-- [ ] I have linked this PR to an issue on the tracker at http://issues.umbraco.org
-
-### Description
-
-
-
-
-
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index e6118de937..d7f81c1bb1 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -19,4 +19,4 @@ using System.Resources;
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.0.0")]
-[assembly: AssemblyInformationalVersion("8.0.0-alpha.49")]
+[assembly: AssemblyInformationalVersion("8.0.0-alpha.52")]
diff --git a/src/Umbraco.Core/Auditing/AuditEventsComponent.cs b/src/Umbraco.Core/Components/AuditEventsComponent.cs
similarity index 78%
rename from src/Umbraco.Core/Auditing/AuditEventsComponent.cs
rename to src/Umbraco.Core/Components/AuditEventsComponent.cs
index 57457f9241..134aa18414 100644
--- a/src/Umbraco.Core/Auditing/AuditEventsComponent.cs
+++ b/src/Umbraco.Core/Components/AuditEventsComponent.cs
@@ -3,7 +3,6 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
-using Umbraco.Core.Components;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
@@ -11,7 +10,7 @@ using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
-namespace Umbraco.Core.Auditing
+namespace Umbraco.Core.Components
{
public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent
{
@@ -53,18 +52,6 @@ namespace Umbraco.Core.Auditing
_userService = userService;
_entityService = entityService;
- //BackOfficeUserManager.AccountLocked += ;
- //BackOfficeUserManager.AccountUnlocked += ;
- BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest;
- BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange;
- BackOfficeUserManager.LoginFailed += OnLoginFailed;
- //BackOfficeUserManager.LoginRequiresVerification += ;
- BackOfficeUserManager.LoginSuccess += OnLoginSuccess;
- BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess;
- BackOfficeUserManager.PasswordChanged += OnPasswordChanged;
- BackOfficeUserManager.PasswordReset += OnPasswordReset;
- //BackOfficeUserManager.ResetAccessFailedCount += ;
-
UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
UserService.SavedUser += OnSavedUser;
@@ -255,64 +242,6 @@ namespace Umbraco.Core.Auditing
"umbraco/user/delete", "delete user");
}
- private void OnLoginSuccess(object sender, EventArgs args)
- {
- if (args is IdentityAuditEventArgs identityArgs)
- {
- var performingUser = GetPerformingUser(identityArgs.PerformingUser);
- WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success");
- }
- }
-
- private void OnLogoutSuccess(object sender, EventArgs args)
- {
- if (args is IdentityAuditEventArgs identityArgs)
- {
- var performingUser = GetPerformingUser(identityArgs.PerformingUser);
- WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success");
- }
- }
-
- private void OnPasswordReset(object sender, EventArgs args)
- {
- if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
- {
- WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset");
- }
- }
-
- private void OnPasswordChanged(object sender, EventArgs args)
- {
- if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
- {
- WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change");
- }
- }
-
- private void OnLoginFailed(object sender, EventArgs args)
- {
- if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
- {
- WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: "");
- }
- }
-
- private void OnForgotPasswordChange(object sender, EventArgs args)
- {
- if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
- {
- WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change");
- }
- }
-
- private void OnForgotPasswordRequest(object sender, EventArgs args)
- {
- if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
- {
- WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request");
- }
- }
-
private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null)
{
var performingUser = _userService.GetUserById(performingId);
diff --git a/src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs
similarity index 91%
rename from src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs
rename to src/Umbraco.Core/Components/ManifestWatcherComponent.cs
index da2f3bba32..0a8d9ccd52 100644
--- a/src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs
+++ b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs
@@ -1,11 +1,9 @@
using System.IO;
-using Umbraco.Core.Components;
-using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
-namespace Umbraco.Core.Strategies
+namespace Umbraco.Core.Components
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent
diff --git a/src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs
similarity index 93%
rename from src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs
rename to src/Umbraco.Core/Components/RelateOnCopyComponent.cs
index 754fc8dca4..4ebd309e9f 100644
--- a/src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs
+++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs
@@ -1,11 +1,9 @@
-using System;
-using Umbraco.Core.Components;
-using Umbraco.Core.Composing;
+using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
-namespace Umbraco.Core.Strategies
+namespace Umbraco.Core.Components
{
//TODO: This should just exist in the content service/repo!
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
diff --git a/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs
similarity index 98%
rename from src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs
rename to src/Umbraco.Core/Components/RelateOnTrashComponent.cs
index 15f0a67a82..fffae85501 100644
--- a/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs
+++ b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs
@@ -1,13 +1,11 @@
-using System;
-using System.Linq;
-using Umbraco.Core.Components;
+using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
-namespace Umbraco.Core.Strategies
+namespace Umbraco.Core.Components
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public sealed class RelateOnTrashComponent : UmbracoComponentBase, IUmbracoCoreComponent
diff --git a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs
index 25399af5c7..f44afe394f 100644
--- a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs
+++ b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs
@@ -38,8 +38,6 @@ namespace Umbraco.Core.Composing.Composers
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
diff --git a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
index 3455c4693e..ed8bece0e1 100644
--- a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
+++ b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
@@ -27,7 +27,6 @@ namespace Umbraco.Core.Composing.Composers
// register the services
container.RegisterSingleton();
container.RegisterSingleton();
- container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs
index 215840023a..cfadb37d6d 100644
--- a/src/Umbraco.Core/Composing/Current.cs
+++ b/src/Umbraco.Core/Composing/Current.cs
@@ -158,6 +158,9 @@ namespace Umbraco.Core.Composing
public static IPublishedValueFallback PublishedValueFallback
=> _publishedValueFallback ?? Container.GetInstance() ?? new NoopPublishedValueFallback();
+ public static IVariationContextAccessor VariationContextAccessor
+ => Container.GetInstance();
+
#endregion
}
}
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
index 332de45734..39861ac4e9 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
@@ -81,6 +81,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("loginBackgroundImage")]
internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty);
+ [ConfigurationProperty("StripUdiAttributes")]
+ internal InnerTextConfigurationElement StripUdiAttributes
+ {
+ get { return GetOptionalTextElement("StripUdiAttributes", true); }
+ }
+
string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress;
@@ -136,6 +142,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes;
+ bool IContentSection.StripUdiAttributes => StripUdiAttributes;
+
string IContentSection.LoginBackgroundImage => LoginBackgroundImage;
}
}
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs
index 519c0e6af1..82cc5928cf 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs
@@ -1,9 +1,24 @@
-using System.Linq;
+using System;
+using System.Linq;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public static class ContentSectionExtensions
{
+ ///
+ /// Gets a value indicating whether the file extension corresponds to an image.
+ ///
+ /// The file extension.
+ ///
+ /// A value indicating whether the file extension corresponds to an image.
+ public static bool IsImageFile(this IContentSection contentConfig, string extension)
+ {
+ if (contentConfig == null) throw new ArgumentNullException(nameof(contentConfig));
+ if (extension == null) return false;
+ extension = extension.TrimStart('.');
+ return contentConfig.ImageFileTypes.InvariantContains(extension);
+ }
+
///
/// Determines if file extension is allowed for upload based on (optional) white list and black list
/// held in settings.
@@ -15,5 +30,17 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
(contentSection.AllowedUploadFiles.Any() == false &&
contentSection.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false);
}
+
+ ///
+ /// Gets the auto-fill configuration for a specified property alias.
+ ///
+ ///
+ /// The property type alias.
+ /// The auto-fill configuration for the specified property alias, or null.
+ public static IImagingAutoFillUploadField GetConfig(this IContentSection contentSection, string propertyTypeAlias)
+ {
+ var autoFillConfigs = contentSection.ImageAutoFillProperties;
+ return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias);
+ }
}
}
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
index 7f6f57f4cf..ef9ffeb014 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
IEnumerable ImageTagAllowedAttributes { get; }
IEnumerable ImageAutoFillProperties { get; }
-
+
string ScriptFolderPath { get; }
IEnumerable ScriptFileTypes { get; }
@@ -66,5 +66,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
bool EnableInheritedMediaTypes { get; }
string LoginBackgroundImage { get; }
+ bool StripUdiAttributes { get; }
+
}
}
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
index 1ce937d31c..f0a986efe2 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
@@ -8,6 +8,8 @@
bool DisableAlternativeTemplates { get; }
+ bool ValidateAlternativeTemplates { get; }
+
bool DisableFindContentByIdPath { get; }
bool DisableRedirectUrlTracking { get; }
@@ -16,5 +18,4 @@
string UmbracoApplicationUrl { get; }
}
-
}
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
index 9f82d15f7d..b4bd821338 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
@@ -5,44 +5,27 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
internal class WebRoutingElement : ConfigurationElement, IWebRoutingSection
{
[ConfigurationProperty("trySkipIisCustomErrors", DefaultValue = "false")]
- public bool TrySkipIisCustomErrors
- {
- get { return (bool) base["trySkipIisCustomErrors"]; }
- }
+ public bool TrySkipIisCustomErrors => (bool) base["trySkipIisCustomErrors"];
[ConfigurationProperty("internalRedirectPreservesTemplate", DefaultValue = "false")]
- public bool InternalRedirectPreservesTemplate
- {
- get { return (bool) base["internalRedirectPreservesTemplate"]; }
- }
+ public bool InternalRedirectPreservesTemplate => (bool) base["internalRedirectPreservesTemplate"];
[ConfigurationProperty("disableAlternativeTemplates", DefaultValue = "false")]
- public bool DisableAlternativeTemplates
- {
- get { return (bool) base["disableAlternativeTemplates"]; }
- }
+ public bool DisableAlternativeTemplates => (bool) base["disableAlternativeTemplates"];
+
+ [ConfigurationProperty("validateAlternativeTemplates", DefaultValue = "false")]
+ public bool ValidateAlternativeTemplates => (bool) base["validateAlternativeTemplates"];
+
[ConfigurationProperty("disableFindContentByIdPath", DefaultValue = "false")]
- public bool DisableFindContentByIdPath
- {
- get { return (bool) base["disableFindContentByIdPath"]; }
- }
+ public bool DisableFindContentByIdPath => (bool) base["disableFindContentByIdPath"];
[ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")]
- public bool DisableRedirectUrlTracking
- {
- get { return (bool) base["disableRedirectUrlTracking"]; }
- }
+ public bool DisableRedirectUrlTracking => (bool) base["disableRedirectUrlTracking"];
[ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")]
- public string UrlProviderMode
- {
- get { return (string) base["urlProviderMode"]; }
- }
+ public string UrlProviderMode => (string) base["urlProviderMode"];
[ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)]
- public string UmbracoApplicationUrl
- {
- get { return (string)base["umbracoApplicationUrl"]; }
- }
+ public string UmbracoApplicationUrl => (string)base["umbracoApplicationUrl"];
}
}
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index 8fb650510b..46ad221837 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -22,7 +22,7 @@ namespace Umbraco.Core.Configuration
///
/// Gets the version comment of the executing code (eg "beta").
///
- public static string CurrentComment => "alpha.49";
+ public static string CurrentComment => "alpha.52";
///
/// Gets the assembly version of Umbraco.Code.dll.
diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs
index 9a1883a065..ac42274a71 100644
--- a/src/Umbraco.Core/Constants-Applications.cs
+++ b/src/Umbraco.Core/Constants-Applications.cs
@@ -13,9 +13,9 @@
public const string Content = "content";
///
- /// Application alias for the developer section.
+ /// Application alias for the packages section.
///
- public const string Developer = "developer";
+ public const string Packages = "packages";
///
/// Application alias for the media section.
diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs
index 7f6f6e875f..eb1a6e2925 100644
--- a/src/Umbraco.Core/Constants-Conventions.cs
+++ b/src/Umbraco.Core/Constants-Conventions.cs
@@ -23,9 +23,6 @@ namespace Umbraco.Core
{
public const string MemberUsernameRuleType = "MemberUsername";
public const string MemberRoleRuleType = "MemberRole";
-
- [Obsolete("No longer supported, this is here for backwards compatibility only")]
- public const string MemberIdRuleType = "MemberId";
}
@@ -94,6 +91,11 @@ namespace Umbraco.Core
/// Property alias for the Media's file extension.
///
public const string Extension = "umbracoExtension";
+
+ ///
+ /// The default height/width of an image file if the size can't be determined from the metadata
+ ///
+ public const int DefaultSize = 200;
}
///
diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs
index 790c143bbf..4bf944e1e1 100644
--- a/src/Umbraco.Core/Constants-ObjectTypes.cs
+++ b/src/Umbraco.Core/Constants-ObjectTypes.cs
@@ -71,9 +71,6 @@ namespace Umbraco.Core
[Obsolete("This no longer exists in the database")]
internal const string Stylesheet = "9F68DA4F-A3A8-44C2-8226-DCBD125E4840";
- [Obsolete("This no longer exists in the database")]
- internal const string StylesheetProperty = "5555da4f-a123-42b2-4488-dcdfb25e4111";
-
// ReSharper restore MemberHidesStaticFromOuterClass
}
diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs
index eaceff275e..abb92298f4 100644
--- a/src/Umbraco.Core/Constants-System.cs
+++ b/src/Umbraco.Core/Constants-System.cs
@@ -58,6 +58,7 @@
///
public const string RecycleBinMediaPathPrefix = "-1,-21,";
+ public const int DefaultLabelDataTypeId = -92;
public const string UmbracoConnectionName = "umbracoDbDSN";
public const string UmbracoUpgradePlanName = "Umbraco.Core";
}
diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs
similarity index 92%
rename from src/Umbraco.Core/Models/ContentExtensions.cs
rename to src/Umbraco.Core/ContentExtensions.cs
index d8eb900bb2..e36731a8cb 100644
--- a/src/Umbraco.Core/Models/ContentExtensions.cs
+++ b/src/Umbraco.Core/ContentExtensions.cs
@@ -7,12 +7,13 @@ using System.Web;
using System.Xml.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
+using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
-namespace Umbraco.Core.Models
+namespace Umbraco.Core
{
public static class ContentExtensions
{
@@ -241,31 +242,6 @@ namespace Umbraco.Core.Models
#region SetValue for setting file contents
- ///
- /// Sets the posted file value of a property.
- ///
- public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null)
- {
- // ensure we get the filename without the path in IE in intranet mode
- // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
- var filename = value.FileName;
- var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture);
- if (pos > 0)
- filename = filename.Substring(pos + 1);
-
- // strip any directory info
- pos = filename.LastIndexOf(IOHelper.DirSepChar);
- if (pos > 0)
- filename = filename.Substring(pos + 1);
-
- // get a safe & clean filename
- filename = IOHelper.SafeFileName(filename);
- if (string.IsNullOrWhiteSpace(filename)) return;
- filename = filename.ToLower(); // fixme - er... why?
-
- MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, culture, segment);
- }
-
///
/// Sets the posted file value of a property.
///
@@ -281,7 +257,31 @@ namespace Umbraco.Core.Models
if (string.IsNullOrWhiteSpace(filename)) return;
filename = filename.ToLower(); // fixme - er... why?
- MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment);
+ SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment);
+ }
+
+ private static void SetUploadFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null)
+ {
+ var property = GetProperty(content, propertyTypeAlias);
+ var oldpath = property.GetValue(culture, segment) is string svalue ? MediaFileSystem.GetRelativePath(svalue) : null;
+ var filepath = MediaFileSystem.StoreFile(content, property.PropertyType, filename, filestream, oldpath);
+ property.SetValue(MediaFileSystem.GetUrl(filepath), culture, segment);
+ }
+
+ // gets or creates a property for a content item.
+ private static Property GetProperty(IContentBase content, string propertyTypeAlias)
+ {
+ var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
+ if (property != null) return property;
+
+ var propertyType = content.GetContentType().CompositionPropertyTypes
+ .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
+ if (propertyType == null)
+ throw new Exception("No property type exists with alias " + propertyTypeAlias + ".");
+
+ property = new Property(propertyType);
+ content.Properties.Add(property);
+ return property;
}
///
diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs
index 0d199d1d0d..c455fadad7 100644
--- a/src/Umbraco.Core/EnumerableExtensions.cs
+++ b/src/Umbraco.Core/EnumerableExtensions.cs
@@ -92,18 +92,7 @@ namespace Umbraco.Core
}
}
- /// The flatten list.
- /// The items.
- /// The select child.
- /// Item type
- /// list of TItem
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("Do not use, use SelectRecursive instead which has far less potential of re-iterating an iterator which may cause significantly more SQL queries")]
- public static IEnumerable FlattenList(this IEnumerable e, Func> f)
- {
- return e.SelectMany(c => f(c).FlattenList(f)).Concat(e);
- }
-
+
///
/// Returns true if all items in the other collection exist in this collection
///
diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs
index 0283ac372e..4c38c0b2ec 100644
--- a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs
+++ b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs
@@ -307,7 +307,7 @@ namespace Umbraco.Core.Events
// fixme see notes above
// delete event args does NOT superceedes 'unpublished' event
- if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished")
+ if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "Unpublished")
return false;
// found occurences, need to determine if this event args is superceded
diff --git a/src/Umbraco.Core/HttpContextExtensions.cs b/src/Umbraco.Core/HttpContextExtensions.cs
index e370b055a4..22eb4d1917 100644
--- a/src/Umbraco.Core/HttpContextExtensions.cs
+++ b/src/Umbraco.Core/HttpContextExtensions.cs
@@ -24,11 +24,19 @@ namespace Umbraco.Core
{
return "Unknown, httpContext is null";
}
- if (httpContext.Request == null)
+
+ HttpRequestBase request;
+ try
+ {
+ // is not null - throws
+ request = httpContext.Request;
+ }
+ catch
{
return "Unknown, httpContext.Request is null";
}
- if (httpContext.Request.ServerVariables == null)
+
+ if (request.ServerVariables == null)
{
return "Unknown, httpContext.Request.ServerVariables is null";
}
@@ -37,16 +45,16 @@ namespace Umbraco.Core
try
{
- var ipAddress = httpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
+ var ipAddress = request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(ipAddress))
- return httpContext.Request.UserHostAddress;
+ return request.UserHostAddress;
var addresses = ipAddress.Split(',');
if (addresses.Length != 0)
return addresses[0];
- return httpContext.Request.UserHostAddress;
+ return request.UserHostAddress;
}
catch (System.Exception ex)
{
diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs
index 794f7f5c20..1b0c8079f0 100644
--- a/src/Umbraco.Core/IO/MediaFileSystem.cs
+++ b/src/Umbraco.Core/IO/MediaFileSystem.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -9,7 +8,6 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
-using Umbraco.Core.Media.Exif;
using Umbraco.Core.Models;
namespace Umbraco.Core.IO
@@ -27,8 +25,6 @@ namespace Umbraco.Core.IO
Logger = Current.Container.GetInstance();
MediaPathScheme = Current.Container.GetInstance();
MediaPathScheme.Initialize(this);
-
- UploadAutoFillProperties = new UploadAutoFillProperties(this, Logger, ContentConfig);
}
private IMediaPathScheme MediaPathScheme { get; }
@@ -36,9 +32,7 @@ namespace Umbraco.Core.IO
private IContentSection ContentConfig { get; }
private ILogger Logger { get; }
-
- internal UploadAutoFillProperties UploadAutoFillProperties { get; }
-
+
///
/// Deletes all files passed in.
///
@@ -205,113 +199,9 @@ namespace Umbraco.Core.IO
return filepath;
}
- // gets or creates a property for a content item.
- private static Property GetProperty(IContentBase content, string propertyTypeAlias)
- {
- var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
- if (property != null) return property;
-
- var propertyType = content.GetContentType().CompositionPropertyTypes
- .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
- if (propertyType == null)
- throw new Exception("No property type exists with alias " + propertyTypeAlias + ".");
-
- property = new Property(propertyType);
- content.Properties.Add(property);
- return property;
- }
-
- // fixme - what's below belongs to the upload property editor, not the media filesystem!
-
- public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null)
- {
- var property = GetProperty(content, propertyTypeAlias);
- var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null;
- var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath);
- property.SetValue(GetUrl(filepath), culture, segment);
- SetUploadFile(content, property, filepath, filestream, culture, segment);
- }
-
- public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, string culture = null, string segment = null)
- {
- var property = GetProperty(content, propertyTypeAlias);
- // fixme delete?
- var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null;
- if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath)
- DeleteFile(oldpath);
- property.SetValue(GetUrl(filepath), culture, segment);
- using (var filestream = OpenFile(filepath))
- {
- SetUploadFile(content, property, filepath, filestream, culture, segment);
- }
- }
-
- // sets a file for the FileUpload property editor
- // ie generates thumbnails and populates autofill properties
- private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, string culture = null, string segment = null)
- {
- // will use filepath for extension, and filestream for length
- UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, culture, segment);
- }
-
- #endregion
-
- #region Image
-
- ///
- /// Gets a value indicating whether the file extension corresponds to an image.
- ///
- /// The file extension.
- /// A value indicating whether the file extension corresponds to an image.
- public bool IsImageFile(string extension)
- {
- if (extension == null) return false;
- extension = extension.TrimStart('.');
- return ContentConfig.ImageFileTypes.InvariantContains(extension);
- }
-
- ///
- /// Gets the dimensions of an image.
- ///
- /// A stream containing the image bytes.
- /// The dimension of the image.
- /// First try with EXIF as it is faster and does not load the entire image
- /// in memory. Fallback to GDI which means loading the image in memory and thus
- /// use potentially large amounts of memory.
- public Size GetDimensions(Stream stream)
- {
- //Try to load with exif
- try
- {
- var jpgInfo = ImageFile.FromStream(stream);
-
- if (jpgInfo.Format != ImageFileFormat.Unknown
- && jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
- && jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
- {
- var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
- var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
- if (height > 0 && width > 0)
- {
- return new Size(width, height);
- }
- }
- }
- catch (Exception)
- {
- //We will just swallow, just means we can't read exif data, we don't want to log an error either
- }
-
- //we have no choice but to try to read in via GDI
- using (var image = Image.FromStream(stream))
- {
-
- var fileWidth = image.Width;
- var fileHeight = image.Height;
- return new Size(fileWidth, fileHeight);
- }
- }
+
#endregion
+
}
}
diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs
index f18a0e68f6..c8eedb1614 100644
--- a/src/Umbraco.Core/IO/SystemDirectories.cs
+++ b/src/Umbraco.Core/IO/SystemDirectories.cs
@@ -40,6 +40,7 @@ namespace Umbraco.Core.IO
public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco");
+ [Obsolete("This will be removed, there is no more umbraco_client folder")]
public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client");
public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols");
diff --git a/src/Umbraco.Core/Logging/DisposableTimer.cs b/src/Umbraco.Core/Logging/DisposableTimer.cs
index db530e5339..ed98e5cfab 100644
--- a/src/Umbraco.Core/Logging/DisposableTimer.cs
+++ b/src/Umbraco.Core/Logging/DisposableTimer.cs
@@ -30,17 +30,17 @@ namespace Umbraco.Core.Logging
_endMessage = endMessage;
_failMessage = failMessage;
_thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds;
- _timingId = Guid.NewGuid().ToString("N");
+ _timingId = Guid.NewGuid().ToString("N").Substring(0, 7); // keep it short-ish
if (thresholdMilliseconds == 0)
{
switch (_level)
{
case LogLevel.Debug:
- logger.Debug(loggerType, "[Timing {TimingId}] {StartMessage}", _timingId, startMessage);
+ logger.Debug(loggerType, "{StartMessage} [Timing {TimingId}]", startMessage, _timingId);
break;
case LogLevel.Information:
- logger.Info(loggerType, "[Timing {TimingId}] {StartMessage}", _timingId, startMessage);
+ logger.Info(loggerType, "{StartMessage} [Timing {TimingId}]", startMessage, _timingId);
break;
default:
throw new ArgumentOutOfRangeException(nameof(level));
@@ -84,15 +84,15 @@ namespace Umbraco.Core.Logging
{
if (_failed)
{
- _logger.Error(_loggerType, _failException, "[Timing {TimingId}] {FailMessage} ({TimingDuration}ms)", _timingId, _failMessage, Stopwatch.ElapsedMilliseconds);
+ _logger.Error(_loggerType, _failException, "{FailMessage} ({Duration}ms) [Timing {TimingId}]", _failMessage, Stopwatch.ElapsedMilliseconds, _timingId);
}
else switch (_level)
{
case LogLevel.Debug:
- _logger.Debug(_loggerType, "[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)", _timingId, _endMessage, Stopwatch.ElapsedMilliseconds);
+ _logger.Debug(_loggerType, "{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, Stopwatch.ElapsedMilliseconds, _timingId);
break;
case LogLevel.Information:
- _logger.Info(_loggerType, "[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)", _timingId, _endMessage, Stopwatch.ElapsedMilliseconds);
+ _logger.Info(_loggerType, "{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, Stopwatch.ElapsedMilliseconds, _timingId);
break;
// filtered in the ctor
//default:
diff --git a/src/Umbraco.Core/Logging/LogHttpRequest.cs b/src/Umbraco.Core/Logging/LogHttpRequest.cs
new file mode 100644
index 0000000000..34c1918b76
--- /dev/null
+++ b/src/Umbraco.Core/Logging/LogHttpRequest.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Web;
+
+namespace Umbraco.Core.Logging
+{
+ public static class LogHttpRequest
+ {
+ static readonly string RequestIdItemName = typeof(LogHttpRequest).Name + "+RequestId";
+
+ ///
+ /// Retrieve the id assigned to the currently-executing HTTP request, if any.
+ ///
+ /// The request id.
+ /// true if there is a request in progress; false otherwise.
+ public static bool TryGetCurrentHttpRequestId(out Guid requestId)
+ {
+ if (HttpContext.Current == null)
+ {
+ requestId = default(Guid);
+ return false;
+ }
+
+ var requestIdItem = HttpContext.Current.Items[RequestIdItemName];
+ if (requestIdItem == null)
+ HttpContext.Current.Items[RequestIdItemName] = requestId = Guid.NewGuid();
+ else
+ requestId = (Guid)requestIdItem;
+
+ return true;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs
new file mode 100644
index 0000000000..2099698b6f
--- /dev/null
+++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs
@@ -0,0 +1,36 @@
+using System;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Umbraco.Core.Logging.Serilog.Enrichers
+{
+ ///
+ /// Enrich log events with a HttpRequestId GUID.
+ /// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestIdEnricher.cs
+ /// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
+ ///
+ internal class HttpRequestIdEnricher : ILogEventEnricher
+ {
+ ///
+ /// The property name added to enriched log events.
+ ///
+ public const string HttpRequestIdPropertyName = "HttpRequestId";
+
+ ///
+ /// Enrich the log event with an id assigned to the currently-executing HTTP request, if any.
+ ///
+ /// The log event to enrich.
+ /// Factory for creating new properties to add to the event.
+ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
+ {
+ if (logEvent == null) throw new ArgumentNullException("logEvent");
+
+ Guid requestId;
+ if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId))
+ return;
+
+ var requestIdProperty = new LogEventProperty(HttpRequestIdPropertyName, new ScalarValue(requestId));
+ logEvent.AddPropertyIfAbsent(requestIdProperty);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs
new file mode 100644
index 0000000000..48415cccbc
--- /dev/null
+++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading;
+using System.Web;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Umbraco.Core.Logging.Serilog.Enrichers
+{
+ ///
+ /// Enrich log events with a HttpRequestNumber unique within the current
+ /// logging session.
+ /// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestNumberEnricher.cs
+ /// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
+ ///
+ internal class HttpRequestNumberEnricher : ILogEventEnricher
+ {
+ ///
+ /// The property name added to enriched log events.
+ ///
+ public const string HttpRequestNumberPropertyName = "HttpRequestNumber";
+
+ static int _lastRequestNumber;
+ static readonly string RequestNumberItemName = typeof(HttpRequestNumberEnricher).Name + "+RequestNumber";
+
+ ///
+ /// Enrich the log event with the number assigned to the currently-executing HTTP request, if any.
+ ///
+ /// The log event to enrich.
+ /// Factory for creating new properties to add to the event.
+ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
+ {
+ if (logEvent == null) throw new ArgumentNullException("logEvent");
+
+ if (HttpContext.Current == null)
+ return;
+
+ int requestNumber;
+ var requestNumberItem = HttpContext.Current.Items[RequestNumberItemName];
+ if (requestNumberItem == null)
+ HttpContext.Current.Items[RequestNumberItemName] = requestNumber = Interlocked.Increment(ref _lastRequestNumber);
+ else
+ requestNumber = (int)requestNumberItem;
+
+ var requestNumberProperty = new LogEventProperty(HttpRequestNumberPropertyName, new ScalarValue(requestNumber));
+ logEvent.AddPropertyIfAbsent(requestNumberProperty);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs
new file mode 100644
index 0000000000..d2fbfd4627
--- /dev/null
+++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs
@@ -0,0 +1,39 @@
+using Serilog.Core;
+using Serilog.Events;
+using System;
+using System.Web;
+
+namespace Umbraco.Core.Logging.Serilog.Enrichers
+{
+ ///
+ /// Enrich log events with the HttpSessionId property.
+ /// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpSessionIdEnricher.cs
+ /// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
+ ///
+ internal class HttpSessionIdEnricher : ILogEventEnricher
+ {
+ ///
+ /// The property name added to enriched log events.
+ ///
+ public const string HttpSessionIdPropertyName = "HttpSessionId";
+
+ ///
+ /// Enrich the log event with the current ASP.NET session id, if sessions are enabled.
+ /// The log event to enrich.
+ /// Factory for creating new properties to add to the event.
+ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
+ {
+ if (logEvent == null) throw new ArgumentNullException("logEvent");
+
+ if (HttpContext.Current == null)
+ return;
+
+ if (HttpContext.Current.Session == null)
+ return;
+
+ var sessionId = HttpContext.Current.Session.SessionID;
+ var sessionIdProperty = new LogEventProperty(HttpSessionIdPropertyName, new ScalarValue(sessionId));
+ logEvent.AddPropertyIfAbsent(sessionIdProperty);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Logging/Serilog/Log4NetLevelMapperEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs
similarity index 96%
rename from src/Umbraco.Core/Logging/Serilog/Log4NetLevelMapperEnricher.cs
rename to src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs
index 1424fa0b55..0c255fa8b4 100644
--- a/src/Umbraco.Core/Logging/Serilog/Log4NetLevelMapperEnricher.cs
+++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs
@@ -1,7 +1,7 @@
using Serilog.Core;
using Serilog.Events;
-namespace Umbraco.Core.Logging.Serilog
+namespace Umbraco.Core.Logging.Serilog.Enrichers
{
///
/// This is used to create a new property in Logs called 'Log4NetLevel'
diff --git a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs
index 8861c808df..2d333ed916 100644
--- a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs
+++ b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs
@@ -3,6 +3,7 @@ using System.Web;
using Serilog;
using Serilog.Events;
using Serilog.Formatting.Compact;
+using Umbraco.Core.Logging.Serilog.Enrichers;
namespace Umbraco.Core.Logging.Serilog
{
@@ -21,7 +22,7 @@ namespace Umbraco.Core.Logging.Serilog
//Set this environment variable - so that it can be used in external config file
//add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" />
Environment.SetEnvironmentVariable("BASEDIR", AppDomain.CurrentDomain.BaseDirectory, EnvironmentVariableTarget.Process);
-
+
logConfig.MinimumLevel.Verbose() //Set to highest level of logging (as any sinks may want to restrict it to Errors only)
.Enrich.WithProcessId()
.Enrich.WithProcessName()
@@ -29,8 +30,11 @@ namespace Umbraco.Core.Logging.Serilog
.Enrich.WithProperty("AppDomainId", AppDomain.CurrentDomain.Id)
.Enrich.WithProperty("AppDomainAppId", HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty))
.Enrich.WithProperty("MachineName", Environment.MachineName)
- .Enrich.With();
-
+ .Enrich.With()
+ .Enrich.With()
+ .Enrich.With()
+ .Enrich.With();
+
return logConfig;
}
diff --git a/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs b/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs
new file mode 100644
index 0000000000..87f104d90e
--- /dev/null
+++ b/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs
@@ -0,0 +1,16 @@
+using System;
+using Newtonsoft.Json.Linq;
+using Umbraco.Core.Models.ContentEditing;
+using Umbraco.Core.Serialization;
+
+namespace Umbraco.Core.Manifest
+{
+ ///
+ /// Implements a json read converter for .
+ ///
+ internal class ContentAppDefinitionConverter : JsonReadConverter
+ {
+ protected override IContentAppDefinition Create(Type objectType, string path, JObject jObject)
+ => new ManifestContentAppDefinition();
+ }
+}
diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs
new file mode 100644
index 0000000000..6b8534a88f
--- /dev/null
+++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text.RegularExpressions;
+using Umbraco.Core.IO;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.ContentEditing;
+
+namespace Umbraco.Core.Manifest
+{
+ // contentApps: [
+ // {
+ // name: 'App Name', // required
+ // alias: 'appAlias', // required
+ // weight: 0, // optional, default is 0, use values between -99 and +99
+ // icon: 'icon.app', // required
+ // view: 'path/view.htm', // required
+ // show: [ // optional, default is always show
+ // '-content/foo', // hide for content type 'foo'
+ // '+content/*', // show for all other content types
+ // '+media/*' // show for all media types
+ // ]
+ // },
+ // ...
+ // ]
+
+ ///
+ /// Represents a content app definition, parsed from a manifest.
+ ///
+ [DataContract(Name = "appdef", Namespace = "")]
+ public class ManifestContentAppDefinition : IContentAppDefinition
+ {
+ private string _view;
+ private ContentApp _app;
+ private ShowRule[] _showRules;
+
+ ///
+ /// Gets or sets the name of the content app.
+ ///
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the unique alias of the content app.
+ ///
+ ///
+ /// Must be a valid javascript identifier, ie no spaces etc.
+ ///
+ [DataMember(Name = "alias")]
+ public string Alias { get; set; }
+
+ ///
+ /// Gets or sets the weight of the content app.
+ ///
+ [DataMember(Name = "weight")]
+ public int Weight { get; set; }
+
+ ///
+ /// Gets or sets the icon of the content app.
+ ///
+ ///
+ /// Must be a valid helveticons class name (see http://hlvticons.ch/).
+ ///
+ [DataMember(Name = "icon")]
+ public string Icon { get; set; }
+
+ ///
+ /// Gets or sets the view for rendering the content app.
+ ///
+ [DataMember(Name = "view")]
+ public string View
+ {
+ get => _view;
+ set => _view = IOHelper.ResolveVirtualUrl(value);
+ }
+
+ ///
+ /// Gets or sets the list of 'show' conditions for the content app.
+ ///
+ [DataMember(Name = "show")]
+ public string[] Show { get; set; } = Array.Empty();
+
+ ///
+ public ContentApp GetContentAppFor(object o)
+ {
+ string partA, partB;
+
+ switch (o)
+ {
+ case IContent content:
+ partA = "content";
+ partB = content.ContentType.Alias;
+ break;
+
+ case IMedia media:
+ partA = "media";
+ partB = media.ContentType.Alias;
+ break;
+
+ default:
+ return null;
+ }
+
+ var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray());
+
+ // if no 'show' is specified, then always display the content app
+ if (rules.Length > 0)
+ {
+ var ok = false;
+
+ // else iterate over each entry
+ foreach (var rule in rules)
+ {
+ // if the entry does not apply, skip it
+ if (!rule.Matches(partA, partB))
+ continue;
+
+ // if the entry applies,
+ // if it's an exclude entry, exit, do not display the content app
+ if (!rule.Show)
+ return null;
+
+ // else break - ok to display
+ ok = true;
+ break;
+ }
+
+ // when 'show' is specified, default is to *not* show the content app
+ if (!ok)
+ return null;
+ }
+
+ // content app can be displayed
+ return _app ?? (_app = new ContentApp
+ {
+ Alias = Alias,
+ Name = Name,
+ Icon = Icon,
+ View = View,
+ Weight = Weight
+ });
+ }
+
+ private class ShowRule
+ {
+ private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ public bool Show { get; private set; }
+ public string PartA { get; private set; }
+ public string PartB { get; private set; }
+
+ public bool Matches(string partA, string partB)
+ {
+ return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB));
+ }
+
+ public static IEnumerable Parse(string[] rules)
+ {
+ foreach (var rule in rules)
+ {
+ var match = ShowRegex.Match(rule);
+ if (!match.Success)
+ throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest.");
+
+ yield return new ShowRule
+ {
+ Show = match.Groups[1].Value != "-",
+ PartA = match.Groups[2].Value,
+ PartB = match.Groups[3].Value
+ };
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs
index e2363e314f..125dee5c05 100644
--- a/src/Umbraco.Core/Manifest/ManifestParser.cs
+++ b/src/Umbraco.Core/Manifest/ManifestParser.cs
@@ -8,6 +8,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
+using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Manifest
@@ -98,6 +99,7 @@ namespace Umbraco.Core.Manifest
var propertyEditors = new List();
var parameterEditors = new List();
var gridEditors = new List();
+ var contentApps = new List();
foreach (var manifest in manifests)
{
@@ -106,6 +108,7 @@ namespace Umbraco.Core.Manifest
if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors);
if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors);
if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors);
+ if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps);
}
return new PackageManifest
@@ -114,7 +117,8 @@ namespace Umbraco.Core.Manifest
Stylesheets = stylesheets.ToArray(),
PropertyEditors = propertyEditors.ToArray(),
ParameterEditors = parameterEditors.ToArray(),
- GridEditors = gridEditors.ToArray()
+ GridEditors = gridEditors.ToArray(),
+ ContentApps = contentApps.ToArray()
};
}
@@ -146,7 +150,8 @@ namespace Umbraco.Core.Manifest
var manifest = JsonConvert.DeserializeObject(text,
new DataEditorConverter(_logger),
- new ValueValidatorConverter(_validators));
+ new ValueValidatorConverter(_validators),
+ new ContentAppDefinitionConverter());
// scripts and stylesheets are raw string, must process here
for (var i = 0; i < manifest.Scripts.Length; i++)
diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs
index a1702cc58b..32dae46a9a 100644
--- a/src/Umbraco.Core/Manifest/PackageManifest.cs
+++ b/src/Umbraco.Core/Manifest/PackageManifest.cs
@@ -1,5 +1,6 @@
using System;
using Newtonsoft.Json;
+using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Manifest
@@ -23,5 +24,8 @@ namespace Umbraco.Core.Manifest
[JsonProperty("gridEditors")]
public GridEditor[] GridEditors { get; set; } = Array.Empty();
+
+ [JsonProperty("contentApps")]
+ public IContentAppDefinition[] ContentApps { get; set; } = Array.Empty();
}
}
diff --git a/src/Umbraco.Core/Media/ImageExtensions.cs b/src/Umbraco.Core/Media/ImageExtensions.cs
deleted file mode 100644
index c20be13d31..0000000000
--- a/src/Umbraco.Core/Media/ImageExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.Linq;
-
-namespace Umbraco.Core.Media
-{
- public static class ImageExtensions
- {
- ///
- /// Gets the MIME type of an image.
- ///
- /// The image.
- /// The MIME type of the image.
- public static string GetMimeType(this Image image)
- {
- var format = image.RawFormat;
- var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
- return codec.MimeType;
- }
- }
-}
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
index ac74e6ee66..ba491fb5e1 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
@@ -69,10 +69,7 @@ namespace Umbraco.Core.Migrations.Install
if (tableName.Equals(Constants.DatabaseSchema.Tables.RelationType))
CreateRelationTypeData();
-
- if (tableName.Equals(Constants.DatabaseSchema.Tables.TaskType))
- CreateTaskTypeData();
-
+
if (tableName.Equals(Constants.DatabaseSchema.Tables.KeyValue))
CreateKeyValueData();
@@ -152,6 +149,7 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" });
+ _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" });
}
private void CreateContentTypeData()
@@ -185,7 +183,7 @@ namespace Umbraco.Core.Migrations.Install
private void CreateUserGroup2AppData()
{
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content });
- _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer });
+ _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Packages });
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media });
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members });
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings });
@@ -232,7 +230,7 @@ namespace Umbraco.Core.Migrations.Install
private void CreateLanguageData()
{
- _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)" });
+ _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true });
}
private void CreateContentChildTypeData()
@@ -318,11 +316,6 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
}
- private void CreateTaskTypeData()
- {
- _database.Insert(Constants.DatabaseSchema.Tables.TaskType, "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" });
- }
-
private void CreateKeyValueData()
{
// on install, initialize the umbraco migration plan with the final state
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
index ae3f95ec4d..d45126f07f 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
@@ -59,8 +59,6 @@ namespace Umbraco.Core.Migrations.Install
typeof (RelationDto),
typeof (TagDto),
typeof (TagRelationshipDto),
- typeof (TaskTypeDto),
- typeof (TaskDto),
typeof (ContentType2ContentTypeDto),
typeof (ContentTypeAllowedContentTypeDto),
typeof (User2NodeNotifyDto),
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index 48ac39a630..eeaf7533a9 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -107,16 +107,37 @@ namespace Umbraco.Core.Migrations.Upgrade
Chain("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}"); // shannon added that one - let's keep it as the default path
//Chain("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one = merge conflict, remove,
- Chain("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but it after shannon's, with a new target state,
+ Chain("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but add it after shannon's, with a new target state,
Add("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}", "{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // and provide a path out of the conflict state
// resume at {4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4} ...
Chain("{1350617A-4930-4D61-852F-E3AA9E692173}");
Chain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0
+ //Chain("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); // andy added that one = merge conflict, remove
Chain("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0
Chain("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0
Chain("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
Chain("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
+ //Chain("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove
+
+ Chain("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state
+ From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // and provide a path out of andy's
+ .CopyChain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // to next
+ // resume at {8B14CEBD-EE47-4AAD-A841-93551D917F11} ...
+
+ Chain("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // add stephan's after others, with a new target state
+ From("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}") // and provide a path out of stephan's
+ .Chain("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // to next
+ // resume at {5F4597F4-A4E0-4AFE-90B5-6D2F896830EB} ...
+
+ //Chain("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
+ Chain("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
+ From("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}")
+ .Chain("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
+ // resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}...
+
+ Chain("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}");
+
//FINAL
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs
index d595c70fa0..7c0b26dd53 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs
@@ -1,4 +1,5 @@
-using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
@@ -23,11 +24,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
private void EnsureLockObject(int id, string name)
{
- var db = Database;
+ EnsureLockObject(Database, id, name);
+ }
+
+ internal static void EnsureLockObject(IUmbracoDatabase db, int id, string name)
+ {
+ // not if it already exists
var exists = db.Exists(id);
if (exists) return;
+
// be safe: delete old umbracoNode lock objects if any
db.Execute($"DELETE FROM umbracoNode WHERE id={id};");
+
// then create umbracoLock object
db.Execute($"INSERT umbracoLock (id, name, value) VALUES ({id}, '{name}', 1);");
}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs
new file mode 100644
index 0000000000..f706671022
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs
@@ -0,0 +1,15 @@
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class DropTaskTables : MigrationBase
+ {
+ public DropTaskTables(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ Delete.Table("cmsTaskType");
+ Delete.Table("cmsTask");
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs
new file mode 100644
index 0000000000..f0d7c02b82
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs
@@ -0,0 +1,24 @@
+using System.Linq;
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ ///
+ /// Adds a new, self-joined field to umbracoLanguages to hold the fall-back language for
+ /// a given language.
+ ///
+ public class FallbackLanguage : MigrationBase
+ {
+ public FallbackLanguage(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
+
+ if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.Language) && x.ColumnName.InvariantEquals("fallbackLanguageId")) == false)
+ AddColumn("fallbackLanguageId");
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs
new file mode 100644
index 0000000000..aa498583ff
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs
@@ -0,0 +1,79 @@
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class RefactorVariantsModel : MigrationBase
+ {
+ public RefactorVariantsModel(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ Delete.Column("edited").FromTable(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation).Do();
+
+
+ // add available column
+ AddColumn("available", out var sqls);
+
+ // so far, only those cultures that were available had records in the table
+ Update.Table(DocumentCultureVariationDto.TableName).Set(new { available = true }).AllRows().Do();
+
+ foreach (var sql in sqls) Execute.Sql(sql).Do();
+
+
+ // add published column
+ AddColumn("published", out sqls);
+
+ // make it false by default
+ Update.Table(DocumentCultureVariationDto.TableName).Set(new { published = false }).AllRows().Do();
+
+ // now figure out whether these available cultures are published, too
+ var getPublished = Sql()
+ .Select(x => x.NodeId)
+ .AndSelect(x => x.LanguageId)
+ .From()
+ .InnerJoin().On((node, cv) => node.NodeId == cv.NodeId)
+ .InnerJoin().On((cv, dv) => cv.Id == dv.Id && dv.Published)
+ .InnerJoin().On((cv, ccv) => cv.Id == ccv.VersionId);
+
+ foreach (var dto in Database.Fetch(getPublished))
+ Database.Execute(Sql()
+ .Update(u => u.Set(x => x.Published, true))
+ .Where(x => x.NodeId == dto.NodeId && x.LanguageId == dto.LanguageId));
+
+ foreach (var sql in sqls) Execute.Sql(sql).Do();
+
+ // so far, it was kinda impossible to make a culture unavailable again,
+ // so we *should* not have anything published but not available - ignore
+
+
+ // add name column
+ AddColumn("name");
+
+ // so far, every record in the table mapped to an available culture
+ var getNames = Sql()
+ .Select(x => x.NodeId)
+ .AndSelect(x => x.LanguageId, x => x.Name)
+ .From()
+ .InnerJoin().On((node, cv) => node.NodeId == cv.NodeId && cv.Current)
+ .InnerJoin().On((cv, ccv) => cv.Id == ccv.VersionId);
+
+ foreach (var dto in Database.Fetch(getNames))
+ Database.Execute(Sql()
+ .Update(u => u.Set(x => x.Name, dto.Name))
+ .Where(x => x.NodeId == dto.NodeId && x.LanguageId == dto.LanguageId));
+ }
+
+ // ReSharper disable once ClassNeverInstantiated.Local
+ // ReSharper disable UnusedAutoPropertyAccessor.Local
+ private class TempDto
+ {
+ public int NodeId { get; set; }
+ public int LanguageId { get; set; }
+ public string Name { get; set; }
+ }
+ // ReSharper restore UnusedAutoPropertyAccessor.Local
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs
new file mode 100644
index 0000000000..b965bc71d2
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs
@@ -0,0 +1,48 @@
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class UpdateDefaultMandatoryLanguage : MigrationBase
+ {
+ public UpdateDefaultMandatoryLanguage(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ // add the new languages lock object
+ AddLockObjects.EnsureLockObject(Database, Constants.Locks.Languages, "Languages");
+
+ // get all existing languages
+ var selectDtos = Sql()
+ .Select()
+ .From();
+
+ var dtos = Database.Fetch(selectDtos);
+
+ // get the id of the language which is already the default one, if any,
+ // else get the lowest language id, which will become the default language
+ var defaultId = int.MaxValue;
+ foreach (var dto in dtos)
+ {
+ if (dto.IsDefault)
+ {
+ defaultId = dto.Id;
+ break;
+ }
+
+ if (dto.Id < defaultId) defaultId = dto.Id;
+ }
+
+ // update, so that language with that id is now default and mandatory
+ var updateDefault = Sql()
+ .Update(u => u
+ .Set(x => x.IsDefault, true)
+ .Set(x => x.IsMandatory, true))
+ .Where(x => x.Id == defaultId);
+
+ Database.Execute(updateDefault);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs
index 174404b1b9..9e223e7beb 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs
@@ -324,9 +324,7 @@ WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1
public const string Tag = "cmsTags";
public const string TagRelationship = "cmsTagRelationship";
-
- public const string Task = "cmsTask";
- public const string TaskType = "cmsTaskType";
+
// ReSharper restore UnusedMember.Local
}
}
diff --git a/src/Umbraco.Core/Models/AuditType.cs b/src/Umbraco.Core/Models/AuditType.cs
index da2c8e5d8e..a5ae34a89d 100644
--- a/src/Umbraco.Core/Models/AuditType.cs
+++ b/src/Umbraco.Core/Models/AuditType.cs
@@ -32,7 +32,7 @@
///
/// Used when nodes are unpublished
///
- UnPublish,
+ Unpublish,
///
/// Used when nodes are moved
///
diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index dd379e02f8..238d87b186 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -269,7 +269,7 @@ namespace Umbraco.Core.Models
if (_publishInfos == null)
_publishInfos = new Dictionary(StringComparer.OrdinalIgnoreCase);
- _publishInfos[culture] = (name, date);
+ _publishInfos[culture.ToLowerInvariant()] = (name, date);
}
private void ClearPublishInfos()
@@ -294,7 +294,7 @@ namespace Umbraco.Core.Models
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_editedCultures == null)
_editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase);
- _editedCultures.Add(culture);
+ _editedCultures.Add(culture.ToLowerInvariant());
}
// sets all publish edited
diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs
index d5d9fcfdac..bf2fd580d9 100644
--- a/src/Umbraco.Core/Models/ContentBase.cs
+++ b/src/Umbraco.Core/Models/ContentBase.cs
@@ -167,7 +167,7 @@ namespace Umbraco.Core.Models
}
///
- public DateTime? GetCultureDate(string culture)
+ public DateTime? GetUpdateDate(string culture)
{
if (culture.IsNullOrWhiteSpace()) return null;
if (!ContentTypeBase.VariesByCulture()) return null;
@@ -202,6 +202,12 @@ namespace Umbraco.Core.Models
}
}
+ internal void TouchCulture(string culture)
+ {
+ if (ContentTypeBase.VariesByCulture() && _cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos))
+ _cultureInfos[culture] = (infos.Name, DateTime.Now);
+ }
+
protected void ClearCultureInfos()
{
_cultureInfos = null;
@@ -280,26 +286,6 @@ namespace Umbraco.Core.Models
Properties.Add(property);
}
- // HttpPostedFileBase is the base class that can be mocked
- // HttpPostedFile is what we get in ASP.NET
- // HttpPostedFileWrapper wraps sealed HttpPostedFile as HttpPostedFileBase
-
- ///
- /// Sets the posted file value of a property.
- ///
- public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, string culture = null, string segment = null)
- {
- ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), culture, segment);
- }
-
- ///
- /// Sets the posted file value of a property.
- ///
- public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null)
- {
- ContentExtensions.SetValue(this, propertyTypeAlias, value, culture, segment);
- }
-
#endregion
#region Copy
diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs
new file mode 100644
index 0000000000..bf28c28c9e
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs
@@ -0,0 +1,72 @@
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Models.ContentEditing
+{
+ ///
+ /// Represents a content app.
+ ///
+ ///
+ /// Content apps are editor extensions.
+ ///
+ [DataContract(Name = "app", Namespace = "")]
+ public class ContentApp
+ {
+ ///
+ /// Gets the name of the content app.
+ ///
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ ///
+ /// Gets the unique alias of the content app.
+ ///
+ ///
+ /// Must be a valid javascript identifier, ie no spaces etc.
+ ///
+ [DataMember(Name = "alias")]
+ public string Alias { get; set; }
+
+ ///
+ /// Gets or sets the weight of the content app.
+ ///
+ ///
+ /// Content apps are ordered by weight, from left (lowest values) to right (highest values).
+ /// Some built-in apps have special weights: listview is -666, content is -100 and infos is +100.
+ /// The default weight is 0, meaning somewhere in-between content and infos, but weight could
+ /// be used for ordering between user-level apps, or anything really.
+ ///
+ [DataMember(Name = "weight")]
+ public int Weight { get; set; }
+
+ ///
+ /// Gets the icon of the content app.
+ ///
+ ///
+ /// Must be a valid helveticons class name (see http://hlvticons.ch/).
+ ///
+ [DataMember(Name = "icon")]
+ public string Icon { get; set; }
+
+ ///
+ /// Gets the view for rendering the content app.
+ ///
+ [DataMember(Name = "view")]
+ public string View { get; set; }
+
+ ///
+ /// The view model specific to this app
+ ///
+ [DataMember(Name = "viewModel")]
+ public object ViewModel { get; set; }
+
+ ///
+ /// Gets a value indicating whether the app is active.
+ ///
+ ///
+ /// Normally reserved for Angular to deal with but in some cases this can be set on the server side.
+ ///
+ [DataMember(Name = "active")]
+ public bool Active { get; set; }
+ }
+}
+
diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs
new file mode 100644
index 0000000000..5e0c421742
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs
@@ -0,0 +1,20 @@
+namespace Umbraco.Core.Models.ContentEditing
+{
+ ///
+ /// Represents a content app definition.
+ ///
+ public interface IContentAppDefinition
+ {
+ ///
+ /// Gets the content app for an object.
+ ///
+ /// The source object.
+ /// The content app for the object, or null.
+ ///
+ /// The definition must determine, based on , whether
+ /// the content app should be displayed or not, and return either a
+ /// instance, or null.
+ ///
+ ContentApp GetContentAppFor(object source);
+ }
+}
diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs
index ff647dc189..e6439acade 100644
--- a/src/Umbraco.Core/Models/ContentType.cs
+++ b/src/Umbraco.Core/Models/ContentType.cs
@@ -98,6 +98,30 @@ namespace Umbraco.Core.Models
}
}
+ ///
+ /// Determines if AllowedTemplates contains templateId
+ ///
+ /// The template id to check
+ /// True if AllowedTemplates contains the templateId else False
+ public bool IsAllowedTemplate(int templateId)
+ {
+ return AllowedTemplates == null
+ ? false
+ : AllowedTemplates.Any(t => t.Id == templateId);
+ }
+
+ ///
+ /// Determines if AllowedTemplates contains templateId
+ ///
+ /// The template alias to check
+ /// True if AllowedTemplates contains the templateAlias else False
+ public bool IsAllowedTemplate(string templateAlias)
+ {
+ return AllowedTemplates == null
+ ? false
+ : AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase));
+ }
+
///
/// Sets the default template for the ContentType
///
diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs
index 9668864588..4f0d0d6c31 100644
--- a/src/Umbraco.Core/Models/DataType.cs
+++ b/src/Umbraco.Core/Models/DataType.cs
@@ -30,6 +30,9 @@ namespace Umbraco.Core.Models
{
_editor = editor ?? throw new ArgumentNullException(nameof(editor));
ParentId = parentId;
+
+ // set a default configuration
+ Configuration = _editor.GetConfigurationEditor().DefaultConfigurationObject;
}
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
diff --git a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs
index 5b63ad81c5..39ece5fa10 100644
--- a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs
+++ b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
namespace Umbraco.Core.Models.Entities
{
@@ -8,13 +9,32 @@ namespace Umbraco.Core.Models.Entities
public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim
{
private static readonly IReadOnlyDictionary Empty = new Dictionary();
+
private IReadOnlyDictionary _cultureNames;
+ private IEnumerable _publishedCultures;
+ private IEnumerable _editedCultures;
+
+ ///
public IReadOnlyDictionary CultureNames
{
get => _cultureNames ?? Empty;
set => _cultureNames = value;
}
+ ///
+ public IEnumerable PublishedCultures
+ {
+ get => _publishedCultures ?? Enumerable.Empty();
+ set => _publishedCultures = value;
+ }
+
+ ///
+ public IEnumerable EditedCultures
+ {
+ get => _editedCultures ?? Enumerable.Empty();
+ set => _editedCultures = value;
+ }
+
public ContentVariation Variations { get; set; }
///
@@ -22,5 +42,6 @@ namespace Umbraco.Core.Models.Entities
///
public bool Edited { get; set; }
+
}
}
diff --git a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs
index dc986a4cd9..9ab557b02c 100644
--- a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs
+++ b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs
@@ -20,4 +20,4 @@
///
string ContentTypeThumbnail { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs
index 6b72fd4a2b..38fd9a02f1 100644
--- a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs
+++ b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs
@@ -7,26 +7,35 @@ namespace Umbraco.Core.Models.Entities
///
public interface IDocumentEntitySlim : IContentEntitySlim
{
- //fixme we need to supply more information than this and change this property name. This will need to include Published/Editor per variation since we need this information for the tree
+ ///
+ /// Gets the variant name for each culture
+ ///
IReadOnlyDictionary CultureNames { get; }
+ ///
+ /// Gets the published cultures.
+ ///
+ IEnumerable PublishedCultures { get; }
+
+ ///
+ /// Gets the edited cultures.
+ ///
+ IEnumerable EditedCultures { get; }
+
+ ///
+ /// Gets the content variation of the content type.
+ ///
ContentVariation Variations { get; }
///
- /// At least one variation is published
+ /// Gets a value indicating whether the content is published.
///
- ///
- /// If the document is invariant, this simply means there is a published version
- ///
- bool Published { get; set; }
+ bool Published { get; }
///
- /// At least one variation has pending changes
+ /// Gets a value indicating whether the content has been edited.
///
- ///
- /// If the document is invariant, this simply means there is pending changes
- ///
- bool Edited { get; set; }
+ bool Edited { get; }
}
}
diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs
index 5910d01d34..d9bc32aaf0 100644
--- a/src/Umbraco.Core/Models/IContent.cs
+++ b/src/Umbraco.Core/Models/IContent.cs
@@ -172,10 +172,9 @@ namespace Umbraco.Core.Models
///
/// A value indicating whether the culture can be published.
///
- /// Fails if values cannot be published, e.g. if some values are not valid.
+ /// Fails if properties don't pass variant validtion rules.
/// Publishing must be finalized via the content service SavePublishing method.
///
- // fixme - should return an attempt with error results
bool PublishCulture(string culture = "*");
///
diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs
index 2cf2d85024..460bd521d4 100644
--- a/src/Umbraco.Core/Models/IContentBase.cs
+++ b/src/Umbraco.Core/Models/IContentBase.cs
@@ -80,13 +80,13 @@ namespace Umbraco.Core.Models
bool IsCultureAvailable(string culture);
///
- /// Gets the date a culture was created.
+ /// Gets the date a culture was updated.
///
///
/// When is null, returns null.
/// If the specified culture is not available, returns null.
///
- DateTime? GetCultureDate(string culture);
+ DateTime? GetUpdateDate(string culture);
///
/// List of properties, which make up all the data available for this Content object
@@ -139,7 +139,7 @@ namespace Umbraco.Core.Models
// fixme validate published cultures?
///
- /// Validates the content item's properties.
+ /// Validates the content item's properties pass variant rules
///
/// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.
diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs
index 951a685d22..99fffdca7b 100644
--- a/src/Umbraco.Core/Models/IContentType.cs
+++ b/src/Umbraco.Core/Models/IContentType.cs
@@ -17,6 +17,20 @@ namespace Umbraco.Core.Models
///
IEnumerable AllowedTemplates { get; set; }
+ ///
+ /// Determines if AllowedTemplates contains templateId
+ ///
+ /// The template id to check
+ /// True if AllowedTemplates contains the templateId else False
+ bool IsAllowedTemplate(int templateId);
+
+ ///
+ /// Determines if AllowedTemplates contains templateId
+ ///
+ /// The template alias to check
+ /// True if AllowedTemplates contains the templateAlias else False
+ bool IsAllowedTemplate(string templateAlias);
+
///
/// Sets the default template for the ContentType
///
diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs
index 7bf9e9b32c..c0d2fed839 100644
--- a/src/Umbraco.Core/Models/ILanguage.cs
+++ b/src/Umbraco.Core/Models/ILanguage.cs
@@ -4,34 +4,54 @@ using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
+ ///
+ /// Represents a language.
+ ///
public interface ILanguage : IEntity, IRememberBeingDirty
{
///
- /// Gets or sets the Iso Code for the Language
+ /// Gets or sets the ISO code of the language.
///
[DataMember]
string IsoCode { get; set; }
///
- /// Gets or sets the Culture Name for the Language
+ /// Gets or sets the culture name of the language.
///
[DataMember]
string CultureName { get; set; }
///
- /// Returns a object for the current Language
+ /// Gets the object for the language.
///
[IgnoreDataMember]
CultureInfo CultureInfo { get; }
///
- /// Defines if this language is the default variant language when language variants are in use
+ /// Gets or sets a value indicating whether the language is the default language.
///
- bool IsDefaultVariantLanguage { get; set; }
+ [DataMember]
+ bool IsDefault { get; set; }
///
- /// If true, a variant node cannot be published unless this language variant is created
+ /// Gets or sets a value indicating whether the language is mandatory.
///
- bool Mandatory { get; set; }
+ ///
+ /// When a language is mandatory, a multi-lingual document cannot be published
+ /// without that language being published, and unpublishing that language unpublishes
+ /// the entire document.
+ ///
+ [DataMember]
+ bool IsMandatory { get; set; }
+
+ ///
+ /// Gets or sets the identifier of a fallback language.
+ ///
+ ///
+ /// The fallback language can be used in multi-lingual scenarios, to help
+ /// define fallback strategies when a value does not exist for a requested language.
+ ///
+ [DataMember]
+ int? FallbackLanguageId { get; set; }
}
}
diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
index b4d9bb2c8a..23c232324a 100644
--- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
+++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
@@ -93,14 +93,6 @@ namespace Umbraco.Core.Models.Identity
_roles.CollectionChanged += _roles_CollectionChanged;
}
- public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager)
- {
- // NOTE the authenticationType must match the umbraco one
- // defined in CookieAuthenticationOptions.AuthenticationType
- var userIdentity = await manager.CreateIdentityAsync(this, Constants.Security.BackOfficeAuthenticationType);
- return userIdentity;
- }
-
///
/// Returns true if an Id has been set on this object this will be false if the object is new and not peristed to the database
///
diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs
index fa1c9dc826..940648c4b9 100644
--- a/src/Umbraco.Core/Models/Language.cs
+++ b/src/Umbraco.Core/Models/Language.cs
@@ -19,6 +19,7 @@ namespace Umbraco.Core.Models
private string _cultureName;
private bool _isDefaultVariantLanguage;
private bool _mandatory;
+ private int? _fallbackLanguageId;
public Language(string isoCode)
{
@@ -30,13 +31,12 @@ namespace Umbraco.Core.Models
{
public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode);
public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName);
- public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefaultVariantLanguage);
- public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory);
+ public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefault);
+ public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.IsMandatory);
+ public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.FallbackLanguageId);
}
- ///
- /// Gets or sets the Iso Code for the Language
- ///
+ ///
[DataMember]
public string IsoCode
{
@@ -44,9 +44,7 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector);
}
- ///
- /// Gets or sets the Culture Name for the Language
- ///
+ ///
[DataMember]
public string CultureName
{
@@ -54,22 +52,29 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector);
}
- ///
- /// Returns a object for the current Language
- ///
+ ///
[IgnoreDataMember]
public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode);
- public bool IsDefaultVariantLanguage
+ ///
+ public bool IsDefault
{
get => _isDefaultVariantLanguage;
set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector);
}
- public bool Mandatory
+ ///
+ public bool IsMandatory
{
get => _mandatory;
set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector);
}
+
+ ///
+ public int? FallbackLanguageId
+ {
+ get => _fallbackLanguageId;
+ set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector);
+ }
}
}
diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs
index 561eb0f3cd..9066674193 100644
--- a/src/Umbraco.Core/Models/Membership/User.cs
+++ b/src/Umbraco.Core/Models/Membership/User.cs
@@ -260,7 +260,7 @@ namespace Umbraco.Core.Models.Membership
{
get
{
- if (LastLoginDate == default(DateTime) && IsApproved == false && InvitedDate != null)
+ if (LastLoginDate == default && IsApproved == false && InvitedDate != null)
return UserState.Invited;
if (IsLockedOut)
@@ -268,6 +268,10 @@ namespace Umbraco.Core.Models.Membership
if (IsApproved == false)
return UserState.Disabled;
+ // User is not disabled or locked and has never logged in before
+ if (LastLoginDate == default && IsApproved && IsLockedOut == false)
+ return UserState.Inactive;
+
return UserState.Active;
}
}
diff --git a/src/Umbraco.Core/Models/Membership/UserState.cs b/src/Umbraco.Core/Models/Membership/UserState.cs
index 0f8d0ec030..fc277b4fa3 100644
--- a/src/Umbraco.Core/Models/Membership/UserState.cs
+++ b/src/Umbraco.Core/Models/Membership/UserState.cs
@@ -9,6 +9,7 @@
Active = 0,
Disabled = 1,
LockedOut = 2,
- Invited = 3
+ Invited = 3,
+ Inactive = 4
}
}
diff --git a/src/Umbraco.Core/Models/PropertyTagChange.cs b/src/Umbraco.Core/Models/PropertyTagChange.cs
deleted file mode 100644
index 5c911f4c30..0000000000
--- a/src/Umbraco.Core/Models/PropertyTagChange.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Umbraco.Core.Models
-{
- ///
- /// A set of tag changes.
- ///
- internal class PropertyTagChange
- {
- public ChangeType Type { get; set; }
-
- public IEnumerable<(string Type, string Tags)> Tags { get; set; }
-
- public enum ChangeType
- {
- Replace,
- Remove,
- Merge
- }
- }
-}
diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs
new file mode 100644
index 0000000000..0434218555
--- /dev/null
+++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Models.PublishedContent
+{
+ ///
+ /// Manages the built-in fallback policies.
+ ///
+ public struct Fallback : IEnumerable
+ {
+ private readonly int[] _values;
+
+ ///
+ /// Initializes a new instance of the struct with values.
+ ///
+ private Fallback(int[] values)
+ {
+ _values = values;
+ }
+
+ ///
+ /// Gets an ordered set of fallback policies.
+ ///
+ ///
+ public static Fallback To(params int[] values) => new Fallback(values);
+
+ ///
+ /// Do not fallback.
+ ///
+ public const int None = 0;
+
+ ///
+ /// Fallback to default value.
+ ///
+ public const int DefaultValue = 1;
+
+ ///
+ /// Gets the fallback to default value policy.
+ ///
+ public static Fallback ToDefaultValue => new Fallback(new[] { DefaultValue });
+
+ ///
+ /// Fallback to other languages.
+ ///
+ public const int Language = 2;
+
+ ///
+ /// Gets the fallback to language policy.
+ ///
+ public static Fallback ToLanguage => new Fallback(new[] { Language });
+
+ ///
+ /// Fallback to tree ancestors.
+ ///
+ public const int Ancestors = 3;
+
+ ///
+ /// Gets the fallback to tree ancestors policy.
+ ///
+ public static Fallback ToAncestors => new Fallback(new[] { Ancestors });
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)_values ?? Array.Empty()).GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs
index 8e1dcfd543..f30a53c8b6 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs
@@ -1,37 +1,122 @@
-using Umbraco.Core.Composing;
-
-namespace Umbraco.Core.Models.PublishedContent
+namespace Umbraco.Core.Models.PublishedContent
{
///
/// Provides a fallback strategy for getting values.
///
- // fixme - IPublishedValueFallback is still WorkInProgress
- // todo - properly document methods, etc
- // todo - understand caching vs fallback (recurse etc)
public interface IPublishedValueFallback
{
- // note that at property level, property.GetValue() does NOT implement fallback, and one has
- // to get property.Value() or property.Value() to trigger fallback
+ ///
+ /// Tries to get a fallback value for a property.
+ ///
+ /// The property.
+ /// The requested culture.
+ /// The requested segment.
+ /// A fallback strategy.
+ /// An optional default value.
+ /// The fallback value.
+ /// A value indicating whether a fallback value could be provided.
+ ///
+ /// This method is called whenever property.Value(culture, segment, defaultValue) is called, and
+ /// property.HasValue(culture, segment) is false.
+ /// It can only fallback at property level (no recurse).
+ /// At property level, property.GetValue() does *not* implement fallback, and one has to
+ /// get property.Value() or property.Value{T}() to trigger fallback.
+ /// Note that and may not be contextualized,
+ /// so the variant context should be used to contextualize them (see our default implementation in
+ /// the web project.
+ ///
+ bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value);
- // this method is called whenever property.Value(culture, segment, defaultValue) is called, and
- // property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
+ ///
+ /// Tries to get a fallback value for a property.
+ ///
+ /// The type of the value.
+ /// The property.
+ /// The requested culture.
+ /// The requested segment.
+ /// A fallback strategy.
+ /// An optional default value.
+ /// The fallback value.
+ /// A value indicating whether a fallback value could be provided.
+ ///
+ /// This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and
+ /// property.HasValue(culture, segment) is false.
+ /// It can only fallback at property level (no recurse).
+ /// At property level, property.GetValue() does *not* implement fallback, and one has to
+ /// get property.Value() or property.Value{T}() to trigger fallback.
+ ///
+ bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value);
- object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue);
+ ///
+ /// Tries to get a fallback value for a published element property.
+ ///
+ /// The published element.
+ /// The property alias.
+ /// The requested culture.
+ /// The requested segment.
+ /// A fallback strategy.
+ /// An optional default value.
+ /// The fallback value.
+ /// A value indicating whether a fallback value could be provided.
+ ///
+ /// This method is called whenever getting the property value for the specified alias, culture and
+ /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.
+ /// It can only fallback at element level (no recurse).
+ ///
+ bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
- // this method is called whenever property.Value(culture, segment, defaultValue) is called, and
- // property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
+ ///
+ /// Tries to get a fallback value for a published element property.
+ ///
+ /// The type of the value.
+ /// The published element.
+ /// The property alias.
+ /// The requested culture.
+ /// The requested segment.
+ /// A fallback strategy.
+ /// An optional default value.
+ /// The fallback value.
+ /// A value indicating whether a fallback value could be provided.
+ ///
+ /// This method is called whenever getting the property value for the specified alias, culture and
+ /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.
+ /// It can only fallback at element level (no recurse).
+ ///
+ bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
- T GetValue(IPublishedProperty property, string culture, string segment, T defaultValue);
+ ///
+ /// Tries to get a fallback value for a published content property.
+ ///
+ /// The published element.
+ /// The property alias.
+ /// The requested culture.
+ /// The requested segment.
+ /// A fallback strategy.
+ /// An optional default value.
+ /// The fallback value.
+ /// A value indicating whether a fallback value could be provided.
+ ///
+ /// This method is called whenever getting the property value for the specified alias, culture and
+ /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.
+ ///
+ bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
- // these methods to be called whenever getting the property value for the specified alias, culture and segment,
- // either returned no property at all, or a property that does not HasValue for the specified culture and segment.
-
- object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue);
-
- T GetValue(IPublishedElement content, string alias, string culture, string segment, T defaultValue);
-
- object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse);
-
- T GetValue(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse);
+ ///
+ /// Tries to get a fallback value for a published content property.
+ ///
+ /// The type of the value.
+ /// The published element.
+ /// The property alias.
+ /// The requested culture.
+ /// The requested segment.
+ /// A fallback strategy.
+ /// An optional default value.
+ /// The fallback value.
+ /// A value indicating whether a fallback value could be provided.
+ ///
+ /// This method is called whenever getting the property value for the specified alias, culture and
+ /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.
+ ///
+ bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
index 962148e138..a366742cc5 100644
--- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
@@ -49,17 +49,24 @@ namespace Umbraco.Core.Models.PublishedContent
/// The model types map.
/// The actual Clr type.
public static Type Map(Type type, Dictionary modelTypes)
+ => Map(type, modelTypes, false);
+
+ public static Type Map(Type type, Dictionary modelTypes, bool dictionaryIsInvariant)
{
+ // it may be that senders forgot to send an invariant dictionary (garbage-in)
+ if (!dictionaryIsInvariant)
+ modelTypes = new Dictionary(modelTypes, StringComparer.InvariantCultureIgnoreCase);
+
if (type is ModelType modelType)
{
- if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out Type actualType))
+ if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out var actualType))
return actualType;
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\".");
}
if (type is ModelTypeArrayType arrayType)
{
- if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out Type actualType))
+ if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out var actualType))
return actualType.MakeArrayType();
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\".");
}
@@ -70,7 +77,7 @@ namespace Umbraco.Core.Models.PublishedContent
if (def == null)
throw new InvalidOperationException("panic");
- var args = type.GetGenericArguments().Select(x => Map(x, modelTypes)).ToArray();
+ var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
return def.MakeGenericType(args);
}
@@ -81,7 +88,14 @@ namespace Umbraco.Core.Models.PublishedContent
/// The model types map.
/// The actual Clr type name.
public static string MapToName(Type type, Dictionary map)
+ => MapToName(type, map, false);
+
+ private static string MapToName(Type type, Dictionary map, bool dictionaryIsInvariant)
{
+ // it may be that senders forgot to send an invariant dictionary (garbage-in)
+ if (!dictionaryIsInvariant)
+ map = new Dictionary(map, StringComparer.InvariantCultureIgnoreCase);
+
if (type is ModelType modelType)
{
if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName))
@@ -102,7 +116,7 @@ namespace Umbraco.Core.Models.PublishedContent
if (def == null)
throw new InvalidOperationException("panic");
- var args = type.GetGenericArguments().Select(x => MapToName(x, map)).ToArray();
+ var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
return defFullName + "<" + string.Join(", ", args) + ">";
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs
index b99b4ad415..cd7b063d44 100644
--- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs
@@ -9,21 +9,45 @@
public class NoopPublishedValueFallback : IPublishedValueFallback
{
///
- public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue) => defaultValue;
+ public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
+ {
+ value = default;
+ return false;
+ }
///
- public T GetValue(IPublishedProperty property, string culture, string segment, T defaultValue) => defaultValue;
+ public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
+ {
+ value = default;
+ return false;
+ }
///
- public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue) => defaultValue;
+ public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
+ {
+ value = default;
+ return false;
+ }
///
- public T GetValue(IPublishedElement content, string alias, string culture, string segment, T defaultValue) => defaultValue;
+ public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
+ {
+ value = default;
+ return false;
+ }
///
- public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) => defaultValue;
+ public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
+ {
+ value = default;
+ return false;
+ }
///
- public T GetValue(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) => defaultValue;
+ public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
+ {
+ value = default;
+ return false;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs
index e0514b38d7..882109f908 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs
@@ -6,7 +6,7 @@
///
/// Every strongly-typed property set class should inherit from PublishedElementModel
/// (or inherit from a class that inherits from... etc.) so they are picked by the factory.
- public class PublishedElementModel : PublishedElementWrapped
+ public abstract class PublishedElementModel : PublishedElementWrapped
{
///
///
diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs
new file mode 100644
index 0000000000..6710d79cc6
--- /dev/null
+++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs
@@ -0,0 +1,15 @@
+namespace Umbraco.Core.Models.PublishedContent
+{
+ public static class VariationContextAccessorExtensions
+ {
+ public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment)
+ {
+ if (culture != null && segment != null) return;
+
+ // use context values
+ var publishedVariationContext = variationContextAccessor?.VariationContext;
+ if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : "";
+ if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : "";
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Section.cs b/src/Umbraco.Core/Models/Section.cs
index cbd9dfe7dc..4b7f8309dd 100644
--- a/src/Umbraco.Core/Models/Section.cs
+++ b/src/Umbraco.Core/Models/Section.cs
@@ -1,26 +1,22 @@
namespace Umbraco.Core.Models
{
///
- /// Represents a section defined in the app.config file
+ /// Represents a section defined in the app.config file.
///
public class Section
{
- public Section(string name, string @alias, string icon, int sortOrder)
+ public Section(string name, string @alias, int sortOrder)
{
Name = name;
Alias = alias;
- Icon = icon;
SortOrder = sortOrder;
}
public Section()
- {
-
- }
+ { }
public string Name { get; set; }
public string Alias { get; set; }
- public string Icon { get; set; }
public int SortOrder { get; set; }
}
}
diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs
deleted file mode 100644
index cb35813619..0000000000
--- a/src/Umbraco.Core/Models/Task.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System;
-using System.Reflection;
-using System.Runtime.Serialization;
-using Umbraco.Core.Models.Entities;
-
-namespace Umbraco.Core.Models
-{
- ///
- /// Represents a Task
- ///
- [Serializable]
- [DataContract(IsReference = true)]
- public class Task : EntityBase
- {
- private bool _closed;
- private TaskType _taskType;
- private int _entityId;
- private int _ownerUserId;
- private int _assigneeUserId;
- private string _comment;
-
- public Task(TaskType taskType)
- {
- _taskType = taskType;
- }
-
- private static readonly Lazy Ps = new Lazy();
-
- private class PropertySelectors
- {
- public readonly PropertyInfo ClosedSelector = ExpressionHelper.GetPropertyInfo(x => x.Closed);
- public readonly PropertyInfo TaskTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.TaskType);
- public readonly PropertyInfo EntityIdSelector = ExpressionHelper.GetPropertyInfo(x => x.EntityId);
- public readonly PropertyInfo OwnerUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.OwnerUserId);
- public readonly PropertyInfo AssigneeUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.AssigneeUserId);
- public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment);
- }
-
- ///
- /// Gets or sets a boolean indicating whether the task is closed
- ///
- [DataMember]
- public bool Closed
- {
- get { return _closed; }
- set { SetPropertyValueAndDetectChanges(value, ref _closed, Ps.Value.ClosedSelector); }
- }
-
- ///
- /// Gets or sets the TaskType of the Task
- ///
- [DataMember]
- public TaskType TaskType
- {
- get { return _taskType; }
- set { SetPropertyValueAndDetectChanges(value, ref _taskType, Ps.Value.TaskTypeSelector); }
- }
-
- ///
- /// Gets or sets the Id of the entity, which this task is associated to
- ///
- [DataMember]
- public int EntityId
- {
- get { return _entityId; }
- set { SetPropertyValueAndDetectChanges(value, ref _entityId, Ps.Value.EntityIdSelector); }
- }
-
- ///
- /// Gets or sets the Id of the user, who owns this task
- ///
- [DataMember]
- public int OwnerUserId
- {
- get { return _ownerUserId; }
- set { SetPropertyValueAndDetectChanges(value, ref _ownerUserId, Ps.Value.OwnerUserIdSelector); }
- }
-
- ///
- /// Gets or sets the Id of the user, who is assigned to this task
- ///
- [DataMember]
- public int AssigneeUserId
- {
- get { return _assigneeUserId; }
- set { SetPropertyValueAndDetectChanges(value, ref _assigneeUserId, Ps.Value.AssigneeUserIdSelector); }
- }
-
- ///
- /// Gets or sets the Comment for the Task
- ///
- [DataMember]
- public string Comment
- {
- get { return _comment; }
- set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); }
- }
-
- }
-}
diff --git a/src/Umbraco.Core/Models/TaskType.cs b/src/Umbraco.Core/Models/TaskType.cs
deleted file mode 100644
index f5e6621239..0000000000
--- a/src/Umbraco.Core/Models/TaskType.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Reflection;
-using System.Runtime.Serialization;
-using Umbraco.Core.Models.Entities;
-
-namespace Umbraco.Core.Models
-{
- ///
- /// Represents a Task Type
- ///
- [Serializable]
- [DataContract(IsReference = true)]
- public class TaskType : EntityBase
- {
- private string _alias;
-
- public TaskType(string @alias)
- {
- _alias = alias;
- }
-
- private static readonly Lazy Ps = new Lazy();
-
- private class PropertySelectors
- {
- public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias);
- }
-
- ///
- /// Gets or sets the Alias of the TaskType
- ///
- [DataMember]
- public string Alias
- {
- get { return _alias; }
- set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); }
- }
- }
-}
diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs
index 9cdbee1951..b7e67c45ea 100644
--- a/src/Umbraco.Core/Models/Template.cs
+++ b/src/Umbraco.Core/Models/Template.cs
@@ -1,14 +1,6 @@
using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
-using System.Text;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.UmbracoSettings;
-using Umbraco.Core.IO;
-using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Models
diff --git a/src/Umbraco.Core/NetworkHelper.cs b/src/Umbraco.Core/NetworkHelper.cs
index e5e153966a..8f310ccf0c 100644
--- a/src/Umbraco.Core/NetworkHelper.cs
+++ b/src/Umbraco.Core/NetworkHelper.cs
@@ -10,9 +10,6 @@ namespace Umbraco.Core
///
/// Returns the machine name that is safe to use in file paths.
///
- ///
- /// see: https://github.com/Shandem/ClientDependency/issues/4
- ///
public static string FileSafeMachineName
{
get { return MachineName.ReplaceNonAlphanumericChars('-'); }
diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
index 79dd45b73b..523d09c1f7 100644
--- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
+++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
@@ -69,10 +69,7 @@ namespace Umbraco.Core
public const string Tag = /*TableNamePrefix*/ "cms" + "Tags";
public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship";
-
- public const string Task = /*TableNamePrefix*/ "cms" + "Task";
- public const string TaskType = /*TableNamePrefix*/ "cms" + "TaskType";
-
+
public const string KeyValue = TableNamePrefix + "KeyValue";
public const string AuditEntry = /*TableNamePrefix*/ "umbraco" + "Audit";
diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs
index 6f5d4bb0dc..1dcd2408e7 100644
--- a/src/Umbraco.Core/Persistence/Constants-Locks.cs
+++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs
@@ -3,17 +3,60 @@ namespace Umbraco.Core
{
static partial class Constants
{
+ ///
+ /// Defines lock objects.
+ ///
public static class Locks
{
+ ///
+ /// All servers.
+ ///
public const int Servers = -331;
+
+ ///
+ /// All content and media types.
+ ///
public const int ContentTypes = -332;
+
+ ///
+ /// The entire content tree, i.e. all content items.
+ ///
public const int ContentTree = -333;
+
+ ///
+ /// The entire media tree, i.e. all media items.
+ ///
public const int MediaTree = -334;
+
+ ///
+ /// The entire member tree, i.e. all members.
+ ///
public const int MemberTree = -335;
+
+ ///
+ /// All media types.
+ ///
public const int MediaTypes = -336;
+
+ ///
+ /// All member types.
+ ///
public const int MemberTypes = -337;
+
+ ///
+ /// All domains.
+ ///
public const int Domains = -338;
+
+ ///
+ /// All key-values.
+ ///
public const int KeyValues = -339;
+
+ ///
+ /// All languages.
+ ///
+ public const int Languages = -340;
}
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs
index a4e51b913e..1dca820a6c 100644
--- a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Dtos
internal class ContentVersionCultureVariationDto
{
public const string TableName = Constants.DatabaseSchema.Tables.ContentVersionCultureVariation;
- private int? _publishedUserId;
+ private int? _updateUserId;
[Column("id")]
[PrimaryKeyColumn]
@@ -33,16 +33,12 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("name")]
public string Name { get; set; }
- [Column("date")]
- public DateTime Date { get; set; }
+ [Column("date")] // fixme: db rename to 'updateDate'
+ public DateTime UpdateDate { get; set; }
- // fixme want?
- [Column("availableUserId")]
+ [Column("availableUserId")] // fixme: db rename to 'updateDate'
[ForeignKey(typeof(UserDto))]
[NullSetting(NullSetting = NullSettings.Null)]
- public int? PublishedUserId { get => _publishedUserId == 0 ? null : _publishedUserId; set => _publishedUserId = value; } //return null if zero
-
- [Column("edited")]
- public bool Edited { get; set; }
+ public int? UpdateUserId { get => _updateUserId == 0 ? null : _updateUserId; set => _updateUserId = value; } //return null if zero
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs
index 3ec40c74b3..a13bf921d9 100644
--- a/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs
@@ -21,11 +21,11 @@ namespace Umbraco.Core.Persistence.Dtos
[ForeignKey(typeof(ContentDto))]
public int NodeId { get; set; }
- [Column("versionDate")]
+ [Column("versionDate")] // fixme: db rename to 'updateDate'
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime VersionDate { get; set; }
- [Column("userId")]
+ [Column("userId")] // fixme: db rename to 'updateUserId'
[ForeignKey(typeof(UserDto))]
[NullSetting(NullSetting = NullSettings.Null)]
public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero
diff --git a/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs b/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs
index 78e819e714..ed61ea5622 100644
--- a/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs
@@ -28,7 +28,25 @@ namespace Umbraco.Core.Persistence.Dtos
[Ignore]
public string Culture { get; set; }
+ // authority on whether a culture has been edited
[Column("edited")]
public bool Edited { get; set; }
+
+ // de-normalized for perfs
+ // (means there is a current content version culture variation for the language)
+ [Column("available")]
+ public bool Available { get; set; }
+
+ // de-normalized for perfs
+ // (means there is a published content version culture variation for the language)
+ [Column("published")]
+ public bool Published { get; set; }
+
+ // de-normalized for perfs
+ // (when available, copies name from current content version culture variation for the language)
+ // (otherwise, it's the published one, 'cos we need to have one)
+ [Column("name")]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ public string Name { get; set; }
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs
index 08bd2a0582..488390f985 100644
--- a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs
@@ -10,33 +10,51 @@ namespace Umbraco.Core.Persistence.Dtos
{
public const string TableName = Constants.DatabaseSchema.Tables.Language;
+ ///
+ /// Gets or sets the identifier of the language.
+ ///
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = 2)]
public short Id { get; set; }
+ ///
+ /// Gets or sets the ISO code of the language.
+ ///
[Column("languageISOCode")]
[Index(IndexTypes.UniqueNonClustered)]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(14)]
public string IsoCode { get; set; }
+ ///
+ /// Gets or sets the culture name of the language.
+ ///
[Column("languageCultureName")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(100)]
public string CultureName { get; set; }
///
- /// Defines if this language is the default variant language when language variants are in use
+ /// Gets or sets a value indicating whether the language is the default language.
///
[Column("isDefaultVariantLang")]
[Constraint(Default = "0")]
- public bool IsDefaultVariantLanguage { get; set; }
+ public bool IsDefault { get; set; }
///
- /// If true, a variant node cannot be published unless this language variant is created
+ /// Gets or sets a value indicating whether the language is mandatory.
///
[Column("mandatory")]
[Constraint(Default = "0")]
- public bool Mandatory { get; set; }
+ public bool IsMandatory { get; set; }
+
+ ///
+ /// Gets or sets the identifier of a fallback language.
+ ///
+ [Column("fallbackLanguageId")]
+ [ForeignKey(typeof(LanguageDto), Column = "id")]
+ [Index(IndexTypes.NonClustered)]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ public int? FallbackLanguageId { get; set; }
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs
index 10450f2bf4..56da821360 100644
--- a/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs
@@ -45,7 +45,7 @@ namespace Umbraco.Core.Persistence.Dtos
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Trashed")]
public bool Trashed { get; set; }
- [Column("nodeUser")] // fixme dbfix rename userId
+ [Column("nodeUser")] // fixme: db rename to 'createUserId'
[ForeignKey(typeof(UserDto))]
[NullSetting(NullSetting = NullSettings.Null)]
public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero
@@ -54,10 +54,10 @@ namespace Umbraco.Core.Persistence.Dtos
[NullSetting(NullSetting = NullSettings.Null)]
public string Text { get; set; }
- [Column("nodeObjectType")] // fixme dbfix rename objectType
+ [Column("nodeObjectType")] // fixme: db rename to 'objectType'
[NullSetting(NullSetting = NullSettings.Null)]
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType")]
- public Guid? NodeObjectType { get; set; } // fixme dbfix rename ObjectType
+ public Guid? NodeObjectType { get; set; }
[Column("createDate")]
[Constraint(Default = SystemMethods.CurrentDateTime)]
diff --git a/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs b/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs
deleted file mode 100644
index 9531ffb7b9..0000000000
--- a/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using NPoco;
-using Umbraco.Core.Persistence.DatabaseAnnotations;
-using Umbraco.Core.Persistence.DatabaseModelDefinitions;
-
-namespace Umbraco.Core.Persistence.Dtos
-{
- [TableName(Constants.DatabaseSchema.Tables.Task)]
- [PrimaryKey("id")]
- [ExplicitColumns]
- internal class TaskDto
- {
- [Column("closed")]
- [Constraint(Default = "0")]
- public bool Closed { get; set; }
-
- [Column("id")]
- [PrimaryKeyColumn]
- public int Id { get; set; }
-
- [Column("taskTypeId")]
- [ForeignKey(typeof(TaskTypeDto))]
- public byte TaskTypeId { get; set; }
-
- [Column("nodeId")]
- [ForeignKey(typeof(NodeDto))]
- public int NodeId { get; set; }
-
- [Column("parentUserId")]
- [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser")]
- public int ParentUserId { get; set; }
-
- [Column("userId")]
- [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser1")]
- public int UserId { get; set; }
-
- [Column("DateTime")]
- [Constraint(Default = SystemMethods.CurrentDateTime)]
- public DateTime DateTime { get; set; }
-
- [Column("Comment")]
- [NullSetting(NullSetting = NullSettings.Null)]
- [Length(500)]
- public string Comment { get; set; }
-
- [ResultColumn]
- [Reference(ReferenceType.OneToOne, ColumnName = "TaskTypeId")]
- public TaskTypeDto TaskTypeDto { get; set; }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs
deleted file mode 100644
index ca731c29c9..0000000000
--- a/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using NPoco;
-using Umbraco.Core.Persistence.DatabaseAnnotations;
-
-namespace Umbraco.Core.Persistence.Dtos
-{
- [TableName(Constants.DatabaseSchema.Tables.TaskType)]
- [PrimaryKey("id")]
- [ExplicitColumns]
- internal class TaskTypeDto
- {
- [Column("id")]
- [PrimaryKeyColumn(IdentitySeed = 2)]
- public byte Id { get; set; }
-
- [Column("alias")]
- [Index(IndexTypes.NonClustered)]
- public string Alias { get; set; }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs
index 7b24411498..ad58c5b570 100644
--- a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs
@@ -8,7 +8,15 @@ namespace Umbraco.Core.Persistence.Factories
{
public static ILanguage BuildEntity(LanguageDto dto)
{
- var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory };
+ var lang = new Language(dto.IsoCode)
+ {
+ CultureName = dto.CultureName,
+ Id = dto.Id,
+ IsDefault = dto.IsDefault,
+ IsMandatory = dto.IsMandatory,
+ FallbackLanguageId = dto.FallbackLanguageId
+ };
+
// reset dirty initial properties (U4-1946)
lang.ResetDirtyProperties(false);
return lang;
@@ -16,9 +24,19 @@ namespace Umbraco.Core.Persistence.Factories
public static LanguageDto BuildDto(ILanguage entity)
{
- var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory };
+ var dto = new LanguageDto
+ {
+ CultureName = entity.CultureName,
+ IsoCode = entity.IsoCode,
+ IsDefault = entity.IsDefault,
+ IsMandatory = entity.IsMandatory,
+ FallbackLanguageId = entity.FallbackLanguageId
+ };
+
if (entity.HasIdentity)
+ {
dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
+ }
return dto;
}
diff --git a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs
deleted file mode 100644
index 49bcbd4262..0000000000
--- a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using Umbraco.Core.Models;
-using Umbraco.Core.Persistence.Dtos;
-
-namespace Umbraco.Core.Persistence.Factories
-{
- ///
- /// Creates the model mappings for Tasks
- ///
- internal static class TaskFactory
- {
- public static Task BuildEntity(TaskDto dto)
- {
- var entity = new Task(new TaskType(dto.TaskTypeDto.Alias) { Id = dto.TaskTypeDto.Id });
-
- try
- {
- entity.DisableChangeTracking();
-
- entity.Closed = dto.Closed;
- entity.AssigneeUserId = dto.UserId;
- entity.Comment = dto.Comment;
- entity.CreateDate = dto.DateTime;
- entity.EntityId = dto.NodeId;
- entity.Id = dto.Id;
- entity.OwnerUserId = dto.ParentUserId;
-
- // reset dirty initial properties (U4-1946)
- entity.ResetDirtyProperties(false);
- return entity;
- }
- finally
- {
- entity.EnableChangeTracking();
- }
- }
-
- public static TaskDto BuildDto(Task entity)
- {
- var dto = new TaskDto
- {
- Closed = entity.Closed,
- Comment = entity.Comment,
- DateTime = entity.CreateDate,
- Id = entity.Id,
- NodeId = entity.EntityId,
- ParentUserId = entity.OwnerUserId,
- TaskTypeId = (byte)entity.TaskType.Id,
- UserId = entity.AssigneeUserId
- };
-
- return dto;
- }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs
deleted file mode 100644
index 01a25ff852..0000000000
--- a/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Umbraco.Core.Models;
-using Umbraco.Core.Persistence.Dtos;
-
-namespace Umbraco.Core.Persistence.Factories
-{
- internal static class TaskTypeFactory
- {
- public static TaskType BuildEntity(TaskTypeDto dto)
- {
- var entity = new TaskType(dto.Alias) {Id = dto.Id};
- // reset dirty initial properties (U4-1946)
- entity.ResetDirtyProperties(false);
- return entity;
- }
-
- public static TaskTypeDto BuildDto(TaskType entity)
- {
- var dto = new TaskTypeDto
- {
- Id = (byte)entity.Id,
- Alias = entity.Alias
- };
- return dto;
- }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs
index f861c450aa..65bfbcdbaf 100644
--- a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs
+++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs
@@ -47,7 +47,6 @@ namespace Umbraco.Core.Persistence.Mappers
Add();
Add();
Add();
- Add();
Add();
Add();
Add();
diff --git a/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs
deleted file mode 100644
index f503fc1684..0000000000
--- a/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Collections.Concurrent;
-using Umbraco.Core.Models;
-using Umbraco.Core.Persistence.Dtos;
-
-namespace Umbraco.Core.Persistence.Mappers
-{
- ///
- /// Represents a to DTO mapper used to translate the properties of the public api
- /// implementation to that of the database's DTO as sql: [tableName].[columnName].
- ///
- [MapperFor(typeof(TaskType))]
- public sealed class TaskTypeMapper : BaseMapper
- {
- private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary();
-
- internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance;
-
- protected override void BuildMap()
- {
- CacheMap(src => src.Id, dto => dto.Id);
- CacheMap(src => src.Alias, dto => dto.Alias);
- }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs
index e40e6dedd3..bd44c095fa 100644
--- a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs
+++ b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs
@@ -22,6 +22,11 @@ namespace Umbraco.Core.Persistence
return databaseType is NPoco.DatabaseTypes.SqlServer2008DatabaseType;
}
+ public static bool IsSqlServer2012OrLater(this DatabaseType databaseType)
+ {
+ return databaseType is NPoco.DatabaseTypes.SqlServer2012DatabaseType;
+ }
+
public static bool IsSqlCe(this DatabaseType databaseType)
{
return databaseType is NPoco.DatabaseTypes.SqlServerCEDatabaseType;
diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
index 9c1f0d9a07..d97c748b6f 100644
--- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
+++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
@@ -7,7 +7,6 @@ using System.Reflection;
using System.Text;
using NPoco;
using Umbraco.Core.Persistence.Querying;
-using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
@@ -74,12 +73,10 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql Where(this Sql sql, Expression> predicate, string alias = null)
{
- var expresionist = new PocoToSqlExpressionVisitor(sql.SqlContext, alias);
- var whereExpression = expresionist.Visit(predicate);
- sql.Where(whereExpression, expresionist.GetSqlParameters());
- return sql;
+ var (s, a) = sql.SqlContext.Visit(predicate, alias);
+ return sql.Where(s, a);
}
-
+
///
/// Appends a WHERE clause to the Sql statement.
///
@@ -92,10 +89,8 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null)
{
- var expresionist = new PocoToSqlExpressionVisitor(sql.SqlContext, alias1, alias2);
- var whereExpression = expresionist.Visit(predicate);
- sql.Where(whereExpression, expresionist.GetSqlParameters());
- return sql;
+ var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2);
+ return sql.Where(s, a);
}
///
@@ -108,7 +103,7 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql WhereIn(this Sql sql, Expression> field, IEnumerable values)
{
- var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax);
+ var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(field);
sql.Where(fieldName + " IN (@values)", new { values });
return sql;
}
@@ -136,7 +131,7 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql WhereNotIn(this Sql sql, Expression> field, IEnumerable values)
{
- var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax);
+ var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(field);
sql.Where(fieldName + " NOT IN (@values)", new { values });
return sql;
}
@@ -164,7 +159,8 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql WhereAnyIn(this Sql sql, Expression>[] fields, IEnumerable values)
{
- var fieldNames = fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
+ var sqlSyntax = sql.SqlContext.SqlSyntax;
+ var fieldNames = fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
var sb = new StringBuilder();
sb.Append("(");
for (var i = 0; i < fieldNames.Length; i++)
@@ -180,7 +176,7 @@ namespace Umbraco.Core.Persistence
private static Sql WhereIn(this Sql sql, Expression> fieldSelector, Sql valuesSql, bool not)
{
- var fieldName = GetFieldName(fieldSelector, sql.SqlContext.SqlSyntax);
+ var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector);
sql.Where(fieldName + (not ? " NOT" : "") +" IN (" + valuesSql.SQL + ")", valuesSql.Arguments);
return sql;
}
@@ -274,7 +270,7 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql OrderBy(this Sql sql, Expression> field)
{
- return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ")");
+ return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ")");
}
///
@@ -286,9 +282,10 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql OrderBy(this Sql sql, params Expression>[] fields)
{
+ var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns(withAlias: false)
- : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
+ : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.OrderBy(columns);
}
@@ -301,7 +298,7 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql OrderByDescending(this Sql sql, Expression> field)
{
- return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ") DESC");
+ return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ") DESC");
}
///
@@ -313,9 +310,10 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql OrderByDescending(this Sql sql, params Expression>[] fields)
{
+ var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns(withAlias: false)
- : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
+ : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.OrderBy(columns.Select(x => x + " DESC"));
}
@@ -339,7 +337,7 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql GroupBy(this Sql sql, Expression> field)
{
- return sql.GroupBy(GetFieldName(field, sql.SqlContext.SqlSyntax));
+ return sql.GroupBy(sql.SqlContext.SqlSyntax.GetFieldName(field));
}
///
@@ -351,9 +349,10 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql GroupBy(this Sql sql, params Expression>[] fields)
{
+ var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns(withAlias: false)
- : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
+ : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.GroupBy(columns);
}
@@ -366,9 +365,10 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql AndBy(this Sql sql, params Expression>[] fields)
{
+ var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns(withAlias: false)
- : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
+ : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.Append(", " + string.Join(", ", columns));
}
@@ -381,9 +381,10 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql AndByDescending(this Sql sql, params Expression>[] fields)
{
+ var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns(withAlias: false)
- : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
+ : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.Append(", " + string.Join(", ", columns.Select(x => x + " DESC")));
}
@@ -391,6 +392,23 @@ namespace Umbraco.Core.Persistence
#region Joins
+ ///
+ /// Appends a CROSS JOIN clause to the Sql statement.
+ ///
+ /// The type of the Dto.
+ /// The Sql statement.
+ /// An optional alias for the joined table.
+ /// The Sql statement.
+ public static Sql CrossJoin(this Sql sql, string alias = null)
+ {
+ var type = typeof(TDto);
+ var tableName = type.GetTableName();
+ var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName);
+ if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias);
+
+ return sql.Append("CROSS JOIN " + join);
+ }
+
///
/// Appends an INNER JOIN clause to the Sql statement.
///
@@ -532,6 +550,25 @@ namespace Umbraco.Core.Persistence
return sqlJoin.On(onExpression, expresionist.GetSqlParameters());
}
+ ///
+ /// Appends an ON clause to a SqlJoin statement.
+ ///
+ /// The type of Dto 1.
+ /// The type of Dto 2.
+ /// The type of Dto 3.
+ /// The SqlJoin statement.
+ /// A predicate to transform and use as the ON clause body.
+ /// An optional alias for Dto 1 table.
+ /// An optional alias for Dto 2 table.
+ /// An optional alias for Dto 3 table.
+ /// The Sql statement.
+ public static Sql On(this Sql.SqlJoinClause sqlJoin, Expression> predicate, string aliasLeft = null, string aliasRight = null, string aliasOther = null)
+ {
+ var expresionist = new PocoToSqlExpressionVisitor(sqlJoin.SqlContext, aliasLeft, aliasRight, aliasOther);
+ var onExpression = expresionist.Visit(predicate);
+ return sqlJoin.On(onExpression, expresionist.GetSqlParameters());
+ }
+
#endregion
#region Select
@@ -572,9 +609,10 @@ namespace Umbraco.Core.Persistence
public static Sql SelectCount(this Sql sql, params Expression>[] fields)
{
if (sql == null) throw new ArgumentNullException(nameof(sql));
+ var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns(withAlias: false)
- : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
+ : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.Select("COUNT (" + string.Join(", ", columns) + ")");
}
@@ -906,7 +944,7 @@ namespace Umbraco.Core.Persistence
public SqlUpd Set(Expression> fieldSelector, object value)
{
- var fieldName = GetFieldName(fieldSelector, _sqlContext.SqlSyntax);
+ var fieldName = _sqlContext.SqlSyntax.GetFieldName(fieldSelector);
_setExpressions.Add(new Tuple(fieldName, value));
return this;
}
@@ -1062,17 +1100,6 @@ namespace Umbraco.Core.Persistence
return string.IsNullOrWhiteSpace(attr?.Name) ? column.Name : attr.Name;
}
- private static string GetFieldName(Expression> fieldSelector, ISqlSyntaxProvider sqlSyntax)
- {
- var field = ExpressionHelper.FindProperty(fieldSelector).Item1 as PropertyInfo;
- var fieldName = field.GetColumnName();
-
- var type = typeof (TDto);
- var tableName = type.GetTableName();
-
- return sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(fieldName);
- }
-
internal static void WriteToConsole(this Sql sql)
{
Console.WriteLine(sql.SQL);
diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
index 4d33977c72..971b65c220 100644
--- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
+++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
@@ -167,4 +167,93 @@ namespace Umbraco.Core.Persistence.Querying
}
}
+ ///
+ /// Represents an expression tree parser used to turn strongly typed expressions into SQL statements.
+ ///
+ /// The type of DTO 1.
+ /// The type of DTO 2.
+ /// The type of DTO 3.
+ /// This visitor is stateful and cannot be reused.
+ internal class PocoToSqlExpressionVisitor : ExpressionVisitorBase
+ {
+ private readonly PocoData _pocoData1, _pocoData2, _pocoData3;
+ private readonly string _alias1, _alias2, _alias3;
+ private string _parameterName1, _parameterName2, _parameterName3;
+
+ public PocoToSqlExpressionVisitor(ISqlContext sqlContext, string alias1, string alias2, string alias3)
+ : base(sqlContext.SqlSyntax)
+ {
+ _pocoData1 = sqlContext.PocoDataFactory.ForType(typeof(TDto1));
+ _pocoData2 = sqlContext.PocoDataFactory.ForType(typeof(TDto2));
+ _pocoData3 = sqlContext.PocoDataFactory.ForType(typeof(TDto3));
+ _alias1 = alias1;
+ _alias2 = alias2;
+ _alias3 = alias3;
+ }
+
+ protected override string VisitLambda(LambdaExpression lambda)
+ {
+ if (lambda.Parameters.Count == 3)
+ {
+ _parameterName1 = lambda.Parameters[0].Name;
+ _parameterName2 = lambda.Parameters[1].Name;
+ _parameterName3 = lambda.Parameters[2].Name;
+ }
+ else if (lambda.Parameters.Count == 2)
+ {
+ _parameterName1 = lambda.Parameters[0].Name;
+ _parameterName2 = lambda.Parameters[1].Name;
+ }
+ else
+ {
+ _parameterName1 = _parameterName2 = null;
+ }
+ return base.VisitLambda(lambda);
+ }
+
+ protected override string VisitMemberAccess(MemberExpression m)
+ {
+ if (m.Expression != null)
+ {
+ if (m.Expression.NodeType == ExpressionType.Parameter)
+ {
+ var pex = (ParameterExpression)m.Expression;
+
+ if (pex.Name == _parameterName1)
+ return Visited ? string.Empty : GetFieldName(_pocoData1, m.Member.Name, _alias1);
+
+ if (pex.Name == _parameterName2)
+ return Visited ? string.Empty : GetFieldName(_pocoData2, m.Member.Name, _alias2);
+
+ if (pex.Name == _parameterName3)
+ return Visited ? string.Empty : GetFieldName(_pocoData3, m.Member.Name, _alias3);
+ }
+ else if (m.Expression.NodeType == ExpressionType.Convert)
+ {
+ // here: which _pd should we use?!
+ throw new NotSupportedException();
+ //return Visited ? string.Empty : GetFieldName(_pd, m.Member.Name);
+ }
+ }
+
+ var member = Expression.Convert(m, typeof(object));
+ var lambda = Expression.Lambda>(member);
+ var getter = lambda.Compile();
+ var o = getter();
+
+ SqlParameters.Add(o);
+
+ // execute if not already compiled
+ return Visited ? string.Empty : "@" + (SqlParameters.Count - 1);
+ }
+
+ protected virtual string GetFieldName(PocoData pocoData, string name, string alias)
+ {
+ var column = pocoData.Columns.FirstOrDefault(x => x.Value.MemberInfoData.Name == name);
+ var tableName = SqlSyntax.GetQuotedTableName(alias ?? pocoData.TableInfo.TableName);
+ var columnName = SqlSyntax.GetQuotedColumnName(column.Value.ColumnName);
+
+ return tableName + "." + columnName;
+ }
+ }
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs
index fea0a61589..f7341d112b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories
{
@@ -67,7 +68,8 @@ namespace Umbraco.Core.Persistence.Repositories
///
/// Gets paged content items.
///
+ /// Here, can be null but cannot.
IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords,
- string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null);
+ IQuery filter, Ordering ordering);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/ITaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITaskRepository.cs
deleted file mode 100644
index 10c517fa72..0000000000
--- a/src/Umbraco.Core/Persistence/Repositories/ITaskRepository.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Collections.Generic;
-using Umbraco.Core.Models;
-
-namespace Umbraco.Core.Persistence.Repositories
-{
- public interface ITaskRepository : IReadWriteQueryRepository
- {
- IEnumerable GetTasks(int? itemId = null, int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false);
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Repositories/ITaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITaskTypeRepository.cs
deleted file mode 100644
index 9fe483b462..0000000000
--- a/src/Umbraco.Core/Persistence/Repositories/ITaskTypeRepository.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Umbraco.Core.Models;
-
-namespace Umbraco.Core.Persistence.Repositories
-{
- public interface ITaskTypeRepository : IReadWriteQueryRepository
- { }
-}
diff --git a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs
index 83d4148689..4bab445bee 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs
@@ -18,25 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories
IEnumerable GetDescendants(int masterTemplateId);
IEnumerable GetDescendants(string alias);
- ///
- /// Returns a template as a template node which can be traversed (parent, children)
- ///
- ///
- ///
- [Obsolete("Use GetDescendants instead")]
- [EditorBrowsable(EditorBrowsableState.Never)]
- TemplateNode GetTemplateNode(string alias);
-
- ///
- /// Given a template node in a tree, this will find the template node with the given alias if it is found in the hierarchy, otherwise null
- ///
- ///
- ///
- ///
- [Obsolete("Use GetDescendants instead")]
- [EditorBrowsable(EditorBrowsableState.Never)]
- TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias);
-
///
/// This checks what the default rendering engine is set in config but then also ensures that there isn't already
/// a template that exists in the opposite rendering engine's template folder, then returns the appropriate
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 2e0139aa30..6ace73fbc3 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -232,16 +232,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- public abstract IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null);
-
- // sql: the main sql
- // filterSql: a filtering ? fixme different from v7?
- // orderBy: the name of an ordering field
- // orderDirection: direction for orderBy
- // orderBySystemField: whether orderBy is a system field or a custom field (property value)
- private Sql PrepareSqlForPage(Sql sql, Sql filterSql, string orderBy, Direction orderDirection, bool orderBySystemField)
+ private Sql PreparePageSql(Sql sql, Sql filterSql, Ordering ordering)
{
- if (filterSql == null && string.IsNullOrEmpty(orderBy)) return sql;
+ // non-filtering, non-ordering = nothing to do
+ if (filterSql == null && ordering.IsEmpty) return sql;
// preserve original
var psql = new Sql(sql.SqlContext, sql.SQL, sql.Arguments);
@@ -251,74 +245,131 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
psql.Append(filterSql);
// non-sorting, we're done
- if (string.IsNullOrEmpty(orderBy))
+ if (ordering.IsEmpty)
return psql;
- // else apply sort
- var dbfield = orderBySystemField
- ? GetOrderBySystemField(ref psql, orderBy)
- : GetOrderByNonSystemField(ref psql, orderBy);
-
- if (orderDirection == Direction.Ascending)
- psql.OrderBy(dbfield);
- else
- psql.OrderByDescending(dbfield);
+ // else apply ordering
+ ApplyOrdering(ref psql, ordering);
// no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique.
// if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
// is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831
- dbfield = GetDatabaseFieldNameForOrderBy("umbracoNode", "id");
- if (orderBySystemField == false || orderBy.InvariantEquals(dbfield) == false)
+ var dbfield = GetQuotedFieldName("umbracoNode", "id");
+ (dbfield, _) = SqlContext.Visit(x => x.NodeId); // fixme?!
+ if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id"))
{
- // get alias, if aliased
- var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL);
- var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(dbfield));
- if (match != null) dbfield = match.Groups[2].Value;
-
- // add field
- psql.OrderBy(dbfield);
+ psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased?
}
// create prepared sql
// ensure it's single-line as NPoco PagingHelper has issues with multi-lines
- psql = new Sql(psql.SqlContext, psql.SQL.ToSingleLine(), psql.Arguments);
+ psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments);
return psql;
}
- private string GetOrderBySystemField(ref Sql sql, string orderBy)
+ private void ApplyOrdering(ref Sql sql, Ordering ordering)
{
- // get the database field eg "[table].[column]"
- var dbfield = GetDatabaseFieldNameForOrderBy(orderBy);
+ if (sql == null) throw new ArgumentNullException(nameof(sql));
+ if (ordering == null) throw new ArgumentNullException(nameof(ordering));
- // for SqlServer pagination to work, the "order by" field needs to be the alias eg if
- // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs
- // to be "NodeDto__Text" and NOT "umbracoNode.text".
- // not sure about SqlCE nor MySql, so better do it too. initially thought about patching
- // NPoco but that would be expensive and not 100% possible, so better give NPoco proper
- // queries to begin with.
- // thought about maintaining a map of columns-to-aliases in the sql context but that would
- // be expensive and most of the time, useless. so instead we parse the SQL looking for the
- // alias. somewhat expensive too but nothing's free.
+ var orderBy = ordering.IsCustomField
+ ? ApplyCustomOrdering(ref sql, ordering)
+ : ApplySystemOrdering(ref sql, ordering);
- // note: ContentTypeAlias is not properly managed because it's not part of the query to begin with!
+ // beware! NPoco paging code parses the query to isolate the ORDER BY fragment,
+ // using a regex that wants "([\w\.\[\]\(\)\s""`,]+)" - meaning that anything
+ // else in orderBy is going to break NPoco / not be detected
- // get alias, if aliased
- var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL);
- var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(dbfield));
- if (match != null) dbfield = match.Groups[2].Value;
+ // beware! NPoco paging code (in PagingHelper) collapses everything [foo].[bar]
+ // to [bar] only, so we MUST use aliases, cannot use [table].[field]
- return dbfield;
+ // beware! pre-2012 SqlServer is using a convoluted syntax for paging, which
+ // includes "SELECT ROW_NUMBER() OVER (ORDER BY ...) poco_rn FROM SELECT (...",
+ // so anything added here MUST also be part of the inner SELECT statement, ie
+ // the original statement, AND must be using the proper alias, as the inner SELECT
+ // will hide the original table.field names entirely
+
+ if (ordering.Direction == Direction.Ascending)
+ sql.OrderBy(orderBy);
+ else
+ sql.OrderByDescending(orderBy);
}
- private string GetOrderByNonSystemField(ref Sql sql, string orderBy)
+ protected virtual string ApplySystemOrdering(ref Sql sql, Ordering ordering)
+ {
+ // id is invariant
+ if (ordering.OrderBy.InvariantEquals("id"))
+ return GetAliasedField(SqlSyntax.GetFieldName(x => x.NodeId), sql);
+
+ // sort order is invariant
+ if (ordering.OrderBy.InvariantEquals("sortOrder"))
+ return GetAliasedField(SqlSyntax.GetFieldName(x => x.SortOrder), sql);
+
+ // path is invariant
+ if (ordering.OrderBy.InvariantEquals("path"))
+ return GetAliasedField(SqlSyntax.GetFieldName(x => x.Path), sql);
+
+ // note: 'owner' is the user who created the item as a whole,
+ // we don't have an 'owner' per culture (should we?)
+ if (ordering.OrderBy.InvariantEquals("owner"))
+ {
+ var joins = Sql()
+ .InnerJoin("ownerUser").On((node, user) => node.UserId == user.Id, aliasRight: "ownerUser");
+
+ // see notes in ApplyOrdering: the field MUST be selected + aliased
+ sql = Sql(InsertBefore(sql, "FROM", ", " + SqlSyntax.GetFieldName(x => x.UserName, "ownerUser") + " AS ordering "), sql.Arguments);
+
+ sql = InsertJoins(sql, joins);
+
+ return "ordering";
+ }
+
+ // note: each version culture variation has a date too,
+ // maybe we would want to use it instead?
+ if (ordering.OrderBy.InvariantEquals("versionDate") || ordering.OrderBy.InvariantEquals("updateDate"))
+ return GetAliasedField(SqlSyntax.GetFieldName(x => x.VersionDate), sql);
+
+ // create date is invariant (we don't keep each culture's creation date)
+ if (ordering.OrderBy.InvariantEquals("createDate"))
+ return GetAliasedField(SqlSyntax.GetFieldName(x => x.CreateDate), sql);
+
+ // name is variant
+ if (ordering.OrderBy.InvariantEquals("name"))
+ {
+ // no culture = can only work on the invariant name
+ // see notes in ApplyOrdering: the field MUST be aliased
+ if (ordering.Culture.IsNullOrWhiteSpace())
+ return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql);
+
+ // culture = must work on variant name ?? invariant name
+ // insert proper join and return coalesced ordering field
+
+ var joins = Sql()
+ .LeftJoin(nested =>
+ nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv")
+ .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv");
+
+ // see notes in ApplyOrdering: the field MUST be selected + aliased
+ sql = Sql(InsertBefore(sql, "FROM", ", " + SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS ordering "), sql.Arguments);
+
+ sql = InsertJoins(sql, joins);
+
+ return "ordering";
+ }
+
+ // previously, we'd accept anything and just sanitize it - not anymore
+ throw new NotSupportedException($"Ordering by {ordering.OrderBy} not supported.");
+ }
+
+ private string ApplyCustomOrdering(ref Sql sql, Ordering ordering)
{
// sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through value
// from 'current' content version for the given order by field
var sortedInt = string.Format(SqlContext.SqlSyntax.ConvertIntegerToOrderableString, "intValue");
+ var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, "decimalValue");
var sortedDate = string.Format(SqlContext.SqlSyntax.ConvertDateToOrderableString, "dateValue");
var sortedString = "COALESCE(varcharValue,'')"; // assuming COALESCE is ok for all syntaxes
- var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, "decimalValue");
// needs to be an outer join since there's no guarantee that any of the nodes have values for this property
var innerSql = Sql().Select($@"CASE
@@ -329,49 +380,60 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
END AS customPropVal,
cver.nodeId AS customPropNodeId")
.From("cver")
- .InnerJoin("opdata").On((left, right) => left.Id == right.Id, "cver", "opdata")
- .InnerJoin("optype").On((left, right) => left.PropertyTypeId == right.Id, "opdata", "optype")
+ .InnerJoin("opdata")
+ .On((version, pdata) => version.Id == pdata.VersionId, "cver", "opdata")
+ .InnerJoin("optype").On((pdata, ptype) => pdata.PropertyTypeId == ptype.Id, "opdata", "optype")
+ .LeftJoin().On((pdata, lang) => pdata.LanguageId == lang.Id, "opdata")
.Where(x => x.Current, "cver") // always query on current (edit) values
- .Where(x => x.Alias == "", "optype");
+ .Where(x => x.Alias == ordering.OrderBy, "optype")
+ .Where((opdata, lang) => opdata.LanguageId == null || lang.IsoCode == ordering.Culture, "opdata");
- // @0 is for x.Current ie 'true' = 1
- // @1 is for x.Alias
- var innerSqlString = innerSql.SQL.Replace("@0", "1").Replace("@1", "@" + sql.Arguments.Length);
+ // merge arguments
+ var argsList = sql.Arguments.ToList();
+ var innerSqlString = ParameterHelper.ProcessParams(innerSql.SQL, innerSql.Arguments, argsList);
+
+ // create the outer join complete sql fragment
var outerJoinTempTable = $@"LEFT OUTER JOIN ({innerSqlString}) AS customPropData
ON customPropData.customPropNodeId = {Constants.DatabaseSchema.Tables.Node}.id "; // trailing space is important!
- // insert this just above the last WHERE
- var pos = sql.SQL.InvariantIndexOf("WHERE");
- if (pos < 0) throw new Exception("Oops, WHERE not found.");
- var newSql = sql.SQL.Insert(pos, outerJoinTempTable);
+ // insert this just above the first WHERE
+ var newSql = InsertBefore(sql.SQL, "WHERE", outerJoinTempTable);
- var newArgs = sql.Arguments.ToList();
- newArgs.Add(orderBy);
+ // see notes in ApplyOrdering: the field MUST be selected + aliased
+ newSql = InsertBefore(newSql, "FROM", ", customPropData.customPropVal AS ordering "); // trailing space is important!
- // insert the SQL selected field, too, else ordering cannot work
- if (sql.SQL.StartsWith("SELECT ") == false) throw new Exception("Oops: SELECT not found.");
- newSql = newSql.Insert("SELECT ".Length, "customPropData.customPropVal, ");
-
- sql = new Sql(sql.SqlContext, newSql, newArgs.ToArray());
+ // create the new sql
+ sql = Sql(newSql, argsList.ToArray());
// and order by the custom field
- return "customPropData.customPropVal";
+ // this original code means that an ascending sort would first expose all NULL values, ie items without a value
+ return "ordering";
+
+ // note: adding an extra sorting criteria on
+ // "(CASE WHEN customPropData.customPropVal IS NULL THEN 1 ELSE 0 END")
+ // would ensure that items without a value always come last, both in ASC and DESC-ending sorts
}
+ public abstract IEnumerable GetPage(IQuery query,
+ long pageIndex, int pageSize, out long totalRecords,
+ IQuery filter,
+ Ordering ordering);
+
+ // here, filter can be null and ordering cannot
protected IEnumerable GetPage(IQuery query,
long pageIndex, int pageSize, out long totalRecords,
Func, IEnumerable> mapDtos,
- string orderBy, Direction orderDirection, bool orderBySystemField,
- Sql filterSql = null) // fixme filter is different on v7?
+ Sql filter,
+ Ordering ordering)
{
- if (orderBy == null) throw new ArgumentNullException(nameof(orderBy));
+ if (ordering == null) throw new ArgumentNullException(nameof(ordering));
// start with base query, and apply the supplied IQuery
- if (query == null) query = AmbientScope.SqlContext.Query();
+ if (query == null) query = Query();
var sql = new SqlTranslator(GetBaseQuery(QueryType.Many), query).Translate();
// sort and filter
- sql = PrepareSqlForPage(sql, filterSql, orderBy, orderDirection, orderBySystemField);
+ sql = PreparePageSql(sql, filter, ordering);
// get a page of DTOs and the total count
var pagedResult = Database.Page(pageIndex + 1, pageSize, sql);
@@ -498,36 +560,48 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return result;
}
- protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy)
- {
- // translate the supplied "order by" field, which were originally defined for in-memory
- // object sorting of ContentItemBasic instance, to the actual database field names.
+ protected string InsertBefore(Sql s, string atToken, string insert)
+ => InsertBefore(s.SQL, atToken, insert);
- switch (orderBy.ToUpperInvariant())
- {
- case "VERSIONDATE":
- case "UPDATEDATE":
- return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.ContentVersion, "versionDate");
- case "CREATEDATE":
- return GetDatabaseFieldNameForOrderBy("umbracoNode", "createDate");
- case "NAME":
- return GetDatabaseFieldNameForOrderBy("umbracoNode", "text");
- case "PUBLISHED":
- return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "published");
- case "OWNER":
- //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter
- return GetDatabaseFieldNameForOrderBy("umbracoNode", "nodeUser");
- case "PATH":
- return GetDatabaseFieldNameForOrderBy("umbracoNode", "path");
- case "SORTORDER":
- return GetDatabaseFieldNameForOrderBy("umbracoNode", "sortOrder");
- default:
- //ensure invalid SQL cannot be submitted
- return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", "");
- }
+ protected string InsertBefore(string s, string atToken, string insert)
+ {
+ var pos = s.InvariantIndexOf(atToken);
+ if (pos < 0) throw new Exception($"Could not find token \"{atToken}\".");
+ return s.Insert(pos, insert);
}
- protected string GetDatabaseFieldNameForOrderBy(string tableName, string fieldName)
+ protected Sql InsertJoins(Sql sql, Sql joins)
+ {
+ var joinsSql = joins.SQL;
+ var args = sql.Arguments;
+
+ // merge args if any
+ if (joins.Arguments.Length > 0)
+ {
+ var argsList = args.ToList();
+ joinsSql = ParameterHelper.ProcessParams(joinsSql, joins.Arguments, argsList);
+ args = argsList.ToArray();
+ }
+
+ return Sql(InsertBefore(sql.SQL, "WHERE", joinsSql), args);
+ }
+
+ private string GetAliasedField(string field, Sql sql)
+ {
+ // get alias, if aliased
+ //
+ // regex looks for pattern "([\w+].[\w+]) AS ([\w+])" ie "(field) AS (alias)"
+ // and, if found & a group's field matches the field name, returns the alias
+ //
+ // so... if query contains "[umbracoNode].[nodeId] AS [umbracoNode__nodeId]"
+ // then GetAliased for "[umbracoNode].[nodeId]" returns "[umbracoNode__nodeId]"
+
+ var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL);
+ var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(field));
+ return match == null ? field : match.Groups[2].Value;
+ }
+
+ protected string GetQuotedFieldName(string tableName, string fieldName)
{
return SqlContext.SqlSyntax.GetQuotedTableName(tableName) + "." + SqlContext.SqlSyntax.GetQuotedColumnName(fieldName);
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index 28c0b0dec6..bf41cd1ad1 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -8,13 +8,12 @@ using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;
-using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
+using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -168,7 +167,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var list = new List
{
"DELETE FROM " + Constants.DatabaseSchema.Tables.RedirectUrl + " WHERE contentKey IN (SELECT uniqueId FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id)",
- "DELETE FROM " + Constants.DatabaseSchema.Tables.Task + " WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.UserGroup2NodePermission + " WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.UserStartNode + " WHERE startNode = @id",
@@ -504,8 +502,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// names also impact 'edited'
foreach (var (culture, name) in content.CultureNames)
if (name != content.GetPublishName(culture))
+ {
+ edited = true;
(editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(culture);
+ // fixme - change tracking
+ // at the moment, we don't do any dirty tracking on property values, so we don't know whether the
+ // culture has just been edited or not, so we don't update its update date - that date only changes
+ // when the name is set, and it all works because the controller does it - but, if someone uses a
+ // service to change a property value and save (without setting name), the update date does not change.
+ }
+
// replace the content version variations (rather than updating)
// only need to delete for the version that existed, the new version (if any) has no property data yet
var deleteContentVariations = Sql().Delete().Where(x => x.VersionId == versionToDelete);
@@ -673,13 +680,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
PermissionRepository.Save(permission);
}
- ///
- /// Gets paged content results.
- ///
+ ///
public override IEnumerable GetPage(IQuery query,
- long pageIndex, int pageSize, out long totalRecords,
- string orderBy, Direction orderDirection, bool orderBySystemField,
- IQuery filter = null)
+ long pageIndex, int pageSize, out long totalRecords,
+ IQuery filter, Ordering ordering)
{
Sql filterSql = null;
@@ -692,8 +696,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return GetPage(query, pageIndex, pageSize, out totalRecords,
x => MapDtosToContent(x),
- orderBy, orderDirection, orderBySystemField,
- filterSql);
+ filterSql,
+ ordering);
}
public bool IsPathPublished(IContent content)
@@ -814,25 +818,59 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- protected override string GetDatabaseFieldNameForOrderBy(string orderBy)
+ protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering)
{
- // NOTE see sortby.prevalues.controller.js for possible values
- // that need to be handled here or in VersionableRepositoryBase
-
- //Some custom ones
- switch (orderBy.ToUpperInvariant())
+ // note: 'updater' is the user who created the latest draft version,
+ // we don't have an 'updater' per culture (should we?)
+ if (ordering.OrderBy.InvariantEquals("updater"))
{
- case "UPDATER":
- // fixme orders by id not letter = bad
- return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.ContentVersion, "userId");
- case "PUBLISHED":
- // fixme kill
- return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "published");
- case "CONTENTTYPEALIAS":
- throw new NotSupportedException("Don't know how to support ContentTypeAlias.");
+ var joins = Sql()
+ .InnerJoin("updaterUser").On((version, user) => version.UserId == user.Id, aliasRight: "updaterUser");
+
+ // see notes in ApplyOrdering: the field MUST be selected + aliased
+ sql = Sql(InsertBefore(sql, "FROM", SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering"), sql.Arguments);
+
+ sql = InsertJoins(sql, joins);
+
+ return "ordering";
}
- return base.GetDatabaseFieldNameForOrderBy(orderBy);
+ if (ordering.OrderBy.InvariantEquals("published"))
+ {
+ // no culture = can only work on the global 'published' flag
+ if (ordering.Culture.IsNullOrWhiteSpace())
+ {
+ // see notes in ApplyOrdering: the field MUST be selected + aliased, and we cannot have
+ // the whole CASE fragment in ORDER BY due to it not being detected by NPoco
+ sql = Sql(InsertBefore(sql, "FROM", ", (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) AS ordering "), sql.Arguments);
+ return "ordering";
+ }
+
+ // invariant: left join will yield NULL and we must use pcv to determine published
+ // variant: left join may yield NULL or something, and that determines published
+
+ var joins = Sql()
+ .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype")
+ .LeftJoin(nested =>
+ nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv")
+ .On((pcv, ccv) => pcv.Id == ccv.VersionId, "pcv", "ccv"); // join on *published* content version
+
+ sql = InsertJoins(sql, joins);
+
+ // see notes in ApplyOrdering: the field MUST be selected + aliased, and we cannot have
+ // the whole CASE fragment in ORDER BY due to it not being detected by NPoco
+ var sqlText = InsertBefore(sql.SQL, "FROM",
+
+ // when invariant, ie 'variations' does not have the culture flag (value 1), use the global 'published' flag on pcv.id,
+ // otherwise check if there's a version culture variation for the lang, via ccv.id
+ ", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccv.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important!
+
+ sql = Sql(sqlText, sql.Arguments);
+
+ return "ordering";
+ }
+
+ return base.ApplySystemOrdering(ref sql, ordering);
}
private IEnumerable MapDtosToContent(List dtos, bool withCache = false)
@@ -1001,7 +1039,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
Culture = LanguageRepository.GetIsoCodeById(dto.LanguageId),
Name = dto.Name,
- Date = dto.Date
+ Date = dto.UpdateDate
});
}
@@ -1046,7 +1084,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = name,
- Date = content.GetCultureDate(culture) ?? DateTime.MinValue // we *know* there is a value
+ UpdateDate = content.GetUpdateDate(culture) ?? DateTime.MinValue // we *know* there is a value
};
// if not publishing, we're just updating the 'current' (non-published) version,
@@ -1061,22 +1099,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = name,
- Date = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value
+ UpdateDate = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value
};
}
private IEnumerable GetDocumentVariationDtos(IContent content, bool publishing, HashSet editedCultures)
{
- foreach (var (culture, name) in content.CultureNames)
+ var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
+ foreach (var culture in allCultures)
yield return new DocumentCultureVariationDto
{
NodeId = content.Id,
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
- // if not published, always edited
- // no need to check for availability: it *is* available since it is in content.CultureNames
- Edited = !content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(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),
+ Edited = content.IsCultureAvailable(culture) &&
+ (!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
};
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
index 340eecb538..fb8c2732e6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
+using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -34,6 +35,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected IUmbracoDatabase Database => _scopeAccessor.AmbientScope.Database;
protected Sql Sql() => _scopeAccessor.AmbientScope.SqlContext.Sql();
+ protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax;
#region Repository
@@ -57,83 +59,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//fixme - we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently
sql = sql.OrderBy("NodeId");
- //IEnumerable result;
- //
- //if (isMedia)
- //{
- // //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag!
- // var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql);
-
- // var ids = pagedResult.Items.Select(x => (int)x.id).InGroupsOf(2000);
- // var entities = pagedResult.Items.Select(BuildEntityFromDynamic).Cast().ToList();
-
- // //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before
- // foreach (var idGroup in ids)
- // {
- // var propSql = GetPropertySql(Constants.ObjectTypes.Media)
- // .WhereIn(x => x.NodeId, idGroup)
- // .OrderBy(x => x.NodeId);
-
- // //This does NOT fetch all data into memory in a list, this will read
- // // over the records as a data reader, this is much better for performance and memory,
- // // but it means that during the reading of this data set, nothing else can be read
- // // from SQL server otherwise we'll get an exception.
- // var allPropertyData = UnitOfWork.Database.Query(propSql);
-
- // //keep track of the current property data item being enumerated
- // var propertyDataSetEnumerator = allPropertyData.GetEnumerator();
- // var hasCurrent = false; // initially there is no enumerator.Current
-
- // try
- // {
- // //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above,
- // // which allows us to more efficiently iterate over the large data set of property values.
- // foreach (var entity in entities)
- // {
- // // assemble the dtos for this def
- // // use the available enumerator.Current if any else move to next
- // while (hasCurrent || propertyDataSetEnumerator.MoveNext())
- // {
- // if (propertyDataSetEnumerator.Current.nodeId == entity.Id)
- // {
- // hasCurrent = false; // enumerator.Current is not available
-
- // //the property data goes into the additional data
- // entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty
- // {
- // PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias,
- // Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.textValue)
- // ? propertyDataSetEnumerator.Current.varcharValue
- // : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.textValue)
- // };
- // }
- // else
- // {
- // hasCurrent = true; // enumerator.Current is available for another def
- // break; // no more propertyDataDto for this def
- // }
- // }
- // }
- // }
- // finally
- // {
- // propertyDataSetEnumerator.Dispose();
- // }
- // }
-
- // result = entities;
- //}
- //else
- //{
- // var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql);
- // result = pagedResult.Items.Select(BuildEntityFromDynamic).Cast().ToList();
- //}
-
var page = Database.Page(pageIndex + 1, pageSize, sql);
var dtos = page.Items;
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray();
-
- //TODO: For isContent will we need to build up the variation info?
+
+ if (isContent)
+ BuildVariants(entities.Cast());
if (isMedia)
BuildProperties(entities, dtos);
@@ -154,11 +85,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
- var dtos = Database.FetchOneToMany(
- ddto => ddto.VariationInfo,
- ddto => ddto.VersionId,
- sql);
- return dtos.Count == 0 ? null : BuildDocumentEntity(dtos[0]);
+ var cdtos = Database.Fetch(sql);
+
+ return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0]));
}
var dto = Database.FirstOrDefault(sql);
@@ -216,13 +145,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
- var cdtos = Database.FetchOneToMany(
- dto => dto.VariationInfo,
- dto => dto.VersionId,
- sql);
+ var cdtos = Database.Fetch