Merge branch 'v9/dev' into v9/task/more-flexible-startup

# Conflicts:
#	src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
This commit is contained in:
Shannon
2021-08-10 13:55:55 -06:00
215 changed files with 3180 additions and 1858 deletions

View File

@@ -1,211 +1,211 @@
# Contributing to Umbraco CMS
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
The following is a set of guidelines, for contributing to Umbraco CMS.
These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request.
Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖.
**Code of conduct**
This project and everyone participating in it, is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk).
**Table of contents**
[Contributing code changes](#contributing-code-changes)
* [Guidelines for contributions we welcome](#guidelines-for-contributions-we-welcome)
* [Ownership and copyright](#ownership-and-copyright)
* [What can I start with?](#what-can-i-start-with)
* [How do I begin?](#how-do-i-begin)
* [Pull requests](#pull-requests)
[Reviews](#reviews)
* [Styleguides](#styleguides)
* [The Core Contributors](#the-core-contributors-team)
* [Questions?](#questions)
[Working with the code](#working-with-the-code)
* [Building Umbraco from source code](#building-umbraco-from-source-code)
* [Working with the source code](#working-with-the-source-code)
* [Making changes after the PR is open](#making-changes-after-the-pr-is-open)
* [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions)
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
## Contributing code changes
This document gives you a quick overview on how to get started.
### Guidelines for contributions we welcome
Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes, so we can ensure that you don't put all your hard work into something we would not be able to merge.
Remember, it is always worth working on an issue from the `Up for grabs` list or even asking for some feedback before you send us a PR. This way, your PR will not be closed as unwanted.
#### Ownership and copyright
It is your responsibility to make sure that you're allowed to share the code you're providing us.
For example, you should have permission from your employer or customer to share code.
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
If you're not sure, leave a note on your contribution and we will be happy to guide you.
When your contribution has been accepted, it will be [MIT licensed](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/LICENSE.md) from that time onwards.
### What can I start with?
Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+)
### How do I begin?
Great question! The short version goes like this:
* **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
![Fork the repository](img/forkrepository.png)
* **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool
![Clone the fork](img/clonefork.png)
* **Switch to the correct branch** - switch to the `v8/contrib` branch
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
* **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first.
* **Push** - great, now you can push the changes up to your fork on GitHub
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
![Create a pull request](img/createpullrequest.png)
### Pull requests
The most successful pull requests usually look a like this:
* Fill in the required template (shown when starting a PR on GitHub), and link your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable.
* Include screenshots and animated GIFs in your pull request whenever possible.
* Unit tests, while optional, are awesome. Thank you!
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated.
Again, these are guidelines, not strict requirements. However, the more information that you give to us, the more we have to work with when considering your contributions. Good documentation of a pull request can really speed up the time it takes to review and merge your work!
## Reviews
You've sent us your first contribution - congratulations! Now what?
The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
We have [a process in place which you can read all about](REVIEW_PROCESS.md). The very abbreviated version is:
- Your PR will get a reply within 48 hours
- An in-depth reply will be added within at most 2 weeks
- The PR will be either merged or rejected within at most 4 weeks
- Sometimes it is difficult to meet these timelines and we'll talk to you if this is the case.
### Styleguides
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
### The Core Contributors team
The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time:
- [Nathan Woulfe](https://github.com/nathanwoulfe)
- [Joe Glombek](https://github.com/glombek)
- [Laura Weatherhead](https://github.com/lssweatherhead)
- [Michael Latouche](https://github.com/mikecp)
- [Owain Williams](https://github.com/OwainWilliams)
These wonderful people aim to provide you with a first reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. Hq will have final sign-off and will check the work again before it is merged.
### Questions?
You can get in touch with [the core contributors team](#the-core-contributors-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
## Working with the code
### Building Umbraco from source code
In order to build the Umbraco source code locally, first make sure you have the following installed.
* [Visual Studio 2019 v16.3+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/)
* [Node.js v10+](https://nodejs.org/en/download/)
* npm v6.4.1+ (installed with Node.js)
* [Git command line](https://git-scm.com/download/)
The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2019 (version 16.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.
Alternatively, you can run `build.ps1` from the Powershell command line, 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.
![Gulp build in Visual Studio](img/gulpbuild.png)
After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser. Follow the directions there to get a working Umbraco install up and running.
### Working with the source code
Some parts of our source code are over 10 years old now. And when we say "old", we mean "mature" of course!
There are two big areas that you should know about:
1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
You may need to run the following commands to set up gulp properly:
```
npm cache clean --force
npm install
npm run build
```
The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to disable caching in the browser to help you to see the changes you're making.
2. "The rest" is a C# based codebase, which is mostly ASP.NET MVC based. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
To find the general areas for something you're looking to fix or improve, have a look at the following two parts of the API documentation.
* [The AngularJS based backoffice files](https://our.umbraco.com/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
* [The C# application](https://our.umbraco.com/apidocs/csharp/)
### Which branch should I target for my contributions?
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
![Which branch should I target?](img/defaultbranch.png)
### Making changes after the PR is open
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
### Keeping your Umbraco fork in sync with the main repository
We recommend you to sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
Also, if you have submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
```
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
```
Then when you want to get the changes from the main repository:
```
git fetch upstream
git rebase upstream/v8/contrib
```
In this command we're syncing with the `v8/contrib` 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))
### And finally
We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Contributors and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
# Contributing to Umbraco CMS
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
The following is a set of guidelines, for contributing to Umbraco CMS.
These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request.
Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖.
**Code of conduct**
This project and everyone participating in it, is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk).
**Table of contents**
[Contributing code changes](#contributing-code-changes)
* [Guidelines for contributions we welcome](#guidelines-for-contributions-we-welcome)
* [Ownership and copyright](#ownership-and-copyright)
* [What can I start with?](#what-can-i-start-with)
* [How do I begin?](#how-do-i-begin)
* [Pull requests](#pull-requests)
[Reviews](#reviews)
* [Styleguides](#styleguides)
* [The Core Contributors](#the-core-contributors-team)
* [Questions?](#questions)
[Working with the code](#working-with-the-code)
* [Building Umbraco from source code](#building-umbraco-from-source-code)
* [Working with the source code](#working-with-the-source-code)
* [Making changes after the PR is open](#making-changes-after-the-pr-is-open)
* [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions)
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
## Contributing code changes
This document gives you a quick overview on how to get started.
### Guidelines for contributions we welcome
Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes, so we can ensure that you don't put all your hard work into something we would not be able to merge.
Remember, it is always worth working on an issue from the `Up for grabs` list or even asking for some feedback before you send us a PR. This way, your PR will not be closed as unwanted.
#### Ownership and copyright
It is your responsibility to make sure that you're allowed to share the code you're providing us.
For example, you should have permission from your employer or customer to share code.
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
If you're not sure, leave a note on your contribution and we will be happy to guide you.
When your contribution has been accepted, it will be [MIT licensed](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/LICENSE.md) from that time onwards.
### What can I start with?
Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+)
### How do I begin?
Great question! The short version goes like this:
* **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
![Fork the repository](img/forkrepository.png)
* **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool
![Clone the fork](img/clonefork.png)
* **Switch to the correct branch** - switch to the `v8/contrib` branch
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
* **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first.
* **Push** - great, now you can push the changes up to your fork on GitHub
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
![Create a pull request](img/createpullrequest.png)
### Pull requests
The most successful pull requests usually look a like this:
* Fill in the required template (shown when starting a PR on GitHub), and link your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable.
* Include screenshots and animated GIFs in your pull request whenever possible.
* Unit tests, while optional, are awesome. Thank you!
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated.
Again, these are guidelines, not strict requirements. However, the more information that you give to us, the more we have to work with when considering your contributions. Good documentation of a pull request can really speed up the time it takes to review and merge your work!
## Reviews
You've sent us your first contribution - congratulations! Now what?
The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
We have [a process in place which you can read all about](REVIEW_PROCESS.md). The very abbreviated version is:
- Your PR will get a reply within 48 hours
- An in-depth reply will be added within at most 2 weeks
- The PR will be either merged or rejected within at most 4 weeks
- Sometimes it is difficult to meet these timelines and we'll talk to you if this is the case.
### Styleguides
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
### The Core Contributors team
The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time:
- [Nathan Woulfe](https://github.com/nathanwoulfe)
- [Joe Glombek](https://github.com/glombek)
- [Laura Weatherhead](https://github.com/lssweatherhead)
- [Michael Latouche](https://github.com/mikecp)
- [Owain Williams](https://github.com/OwainWilliams)
These wonderful people aim to provide you with a first reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. Hq will have final sign-off and will check the work again before it is merged.
### Questions?
You can get in touch with [the core contributors team](#the-core-contributors-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
## Working with the code
### Building Umbraco from source code
In order to build the Umbraco source code locally, first make sure you have the following installed.
* [Visual Studio 2019 v16.3+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/)
* [Node.js v10+](https://nodejs.org/en/download/)
* npm v6.4.1+ (installed with Node.js)
* [Git command line](https://git-scm.com/download/)
The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2019 (version 16.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.
Alternatively, you can run `build.ps1` from the Powershell command line, 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.
![Gulp build in Visual Studio](img/gulpbuild.png)
After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser. Follow the directions there to get a working Umbraco install up and running.
### Working with the source code
Some parts of our source code are over 10 years old now. And when we say "old", we mean "mature" of course!
There are two big areas that you should know about:
1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
You may need to run the following commands to set up gulp properly:
```
npm cache clean --force
npm install
npm run build
```
The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to disable caching in the browser to help you to see the changes you're making.
2. "The rest" is a C# based codebase, which is mostly ASP.NET MVC based. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
To find the general areas for something you're looking to fix or improve, have a look at the following two parts of the API documentation.
* [The AngularJS based backoffice files](https://apidocs.umbraco.com/v9/ui#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
* [The C# application](https://apidocs.umbraco.com/v9/csharp/)
### Which branch should I target for my contributions?
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
![Which branch should I target?](img/defaultbranch.png)
### Making changes after the PR is open
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
### Keeping your Umbraco fork in sync with the main repository
We recommend you to sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
Also, if you have submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
```
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
```
Then when you want to get the changes from the main repository:
```
git fetch upstream
git rebase upstream/v8/contrib
```
In this command we're syncing with the `v8/contrib` 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))
### And finally
We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Contributors and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.

5
.gitignore vendored
View File

@@ -207,6 +207,7 @@ src/Umbraco.Tests/TEMP/
src/Umbraco.Tests.UnitTests/umbraco/Data/TEMP/
/src/Umbraco.Web.UI.NetCore/appsettings.Local.json
src/Umbraco.Tests.Integration/DatabaseContextTests.sdf
src/Umbraco.Tests.Integration.SqlCe/umbraco/Data/TEMP/
src/Umbraco.Tests.Integration.SqlCe/DatabaseContextTests.sdf
src/Umbraco.Web.UI.NetCore/umbraco/config/appsettings-schema.json
src/Umbraco.Web.UI.NetCore/umbraco/config/appsettings-schema.json

View File

@@ -123,7 +123,7 @@ stages:
displayName: dotnet test
inputs:
command: test
projects: '**\Umbraco.Tests.Integration.csproj'
projects: '**\Umbraco.Tests.Integration*.csproj'
- stage: Acceptance_Tests
displayName: Acceptance Tests
dependsOn: []

View File

@@ -503,7 +503,7 @@
Pop-Location
# change baseUrl
$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
$BaseUrl = "https://apidocs.umbraco.com/v9/ui/"
$IndexPath = "./api/index.html"
(Get-Content $IndexPath).replace('origin + location.href.substr(origin.length).replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath

View File

@@ -24,7 +24,7 @@
"version": {
"type": "parameter",
"datatype": "string",
"defaultValue": "9.0.0-beta003",
"defaultValue": "9.0.0-rc001",
"description": "The version of Umbraco to load using NuGet",
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
},

View File

@@ -57,7 +57,7 @@
"version": {
"type": "parameter",
"datatype": "string",
"defaultValue": "9.0.0-beta004",
"defaultValue": "9.0.0-rc001",
"description": "The version of Umbraco to load using NuGet",
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
},

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">Umbraco.Cms.Web.UI.NetCore</RootNamespace>
@@ -15,6 +15,11 @@
<ProjectReference Include="..\PackageTestSiteName\PackageTestSiteName.csproj"/>
</ItemGroup>
<ItemGroup>
<Content Remove="umbraco\Data\**" />
<Content Remove="umbraco\logs\**" />
<Content Remove="umbraco\MediaCache\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="umbraco\Data\**" />
<Compile Remove="umbraco\logs\**" />
@@ -26,9 +31,6 @@
<EmbeddedResource Remove="umbraco\MediaCache\**" />
</ItemGroup>
<ItemGroup>
<None Remove="umbraco\Data\**" />
<None Remove="umbraco\logs\**" />
<None Remove="umbraco\MediaCache\**" />
<None Include="config\**\*.*">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
@@ -37,11 +39,9 @@
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Remove="umbraco\Data\**" />
<Content Remove="umbraco\logs\**" />
<Content Remove="umbraco\MediaCache\**" />
<None Remove="umbraco\Data\**" />
<None Remove="umbraco\logs\**" />
<None Remove="umbraco\MediaCache\**" />
</ItemGroup>
<!-- Set this to true if ModelsBuilder mode is not InMemoryAuto-->

View File

@@ -1,4 +1,6 @@
@using Umbraco.Web.UI.NetCore
@using Umbraco.Extensions
@using Umbraco.Web.PublishedModels
@using Umbraco.Cms.Core.Models.PublishedContent
@using Microsoft.AspNetCore.Html
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<Version>9.0.0</Version>
<AssemblyVersion>9.0.0</AssemblyVersion>
<InformationalVersion>9.0.0-rc001.pre001</InformationalVersion>
<InformationalVersion>9.0.0-rc001</InformationalVersion>
<FileVersion>9.0.0</FileVersion>
<LangVersion Condition="'$(LangVersion)' == ''">9.0</LangVersion>
<NeutralLanguage>en-US</NeutralLanguage>

View File

@@ -46,6 +46,7 @@ namespace JsonSchema
public UnattendedSettings Unattended { get; set; }
public RichTextEditorSettings RichTextEditor { get; set; }
public RuntimeMinificationSettings RuntimeMinification { get; set; }
public BasicAuthSettings BasicAuth { get; set; }
}
/// <summary>

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="NJsonSchema" Version="10.4.4" />
<PackageReference Include="NJsonSchema" Version="10.5.2" />
<PackageReference Include="System.Xml.XPath.XmlDocument" Version="4.3.0" />
<PackageReference Include="Umbraco.Forms.Core" Version="9.0.0-preview20210624.66481" />
</ItemGroup>

View File

@@ -11,9 +11,10 @@ namespace Umbraco.Extensions
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a published snapshot service.</param>
public static void SetPublishedSnapshotService(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedSnapshotService> factory)
public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedSnapshotService> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -21,10 +22,11 @@ namespace Umbraco.Extensions
/// </summary>
/// <typeparam name="T">The type of the published snapshot service.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetPublishedSnapshotService<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetPublishedSnapshotService<T>(this IUmbracoBuilder builder)
where T : class, IPublishedSnapshotService
{
builder.Services.AddUnique<IPublishedSnapshotService, T>();
return builder;
}
/// <summary>
@@ -32,9 +34,10 @@ namespace Umbraco.Extensions
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="service">A published snapshot service.</param>
public static void SetPublishedSnapshotService(this IUmbracoBuilder builder, IPublishedSnapshotService service)
public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, IPublishedSnapshotService service)
{
builder.Services.AddUnique(service);
return builder;
}
}
}

View File

@@ -146,6 +146,7 @@ namespace Umbraco.Cms.Core.Composing
"HtmlDiff,",
"ICSharpCode.",
"Iesi.Collections,", // used by NHibernate
"JetBrains.Annotations,",
"LightInject.", // DI
"LightInject,",
"Lucene.",

View File

@@ -17,48 +17,57 @@ namespace Umbraco.Cms.Core.Configuration
public string ProviderName { get; }
public string Name { get; }
private string ParseProvider(string connectionString)
private static bool IsSqlCe(DbConnectionStringBuilder builder) => (builder.TryGetValue("Data Source", out var ds)
|| builder.TryGetValue("DataSource", out ds)) &&
ds is string dataSource &&
dataSource.EndsWith(".sdf");
private static bool IsSqlServer(DbConnectionStringBuilder builder) =>
!string.IsNullOrEmpty(GetServer(builder)) &&
((builder.TryGetValue("Database", out var db) && db is string database &&
!string.IsNullOrEmpty(database)) ||
(builder.TryGetValue("AttachDbFileName", out var a) && a is string attachDbFileName &&
!string.IsNullOrEmpty(attachDbFileName)) ||
(builder.TryGetValue("Initial Catalog", out var i) && i is string initialCatalog &&
!string.IsNullOrEmpty(initialCatalog)));
private static string GetServer(DbConnectionStringBuilder builder)
{
if(builder.TryGetValue("Server", out var s) && s is string server)
{
return server;
}
if ((builder.TryGetValue("Data Source", out var ds)
|| builder.TryGetValue("DataSource", out ds)) && ds is string dataSource)
{
return dataSource;
}
return "";
}
private static string ParseProvider(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
return null;
}
var builder = new DbConnectionStringBuilder
var builder = new DbConnectionStringBuilder {ConnectionString = connectionString};
if (IsSqlCe(builder))
{
ConnectionString = connectionString
};
if (
(builder.TryGetValue("Data Source", out var ds)
|| builder.TryGetValue("DataSource", out ds)) && ds is string dataSource)
{
if (dataSource.EndsWith(".sdf"))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlCe;
}
return Constants.DbProviderNames.SqlCe;
}
if (builder.TryGetValue("Server", out var s) && s is string server && !string.IsNullOrEmpty(server))
if (IsSqlServer(builder))
{
if (builder.TryGetValue("Database", out var db) && db is string database && !string.IsNullOrEmpty(database))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlServer;
}
if (builder.TryGetValue("AttachDbFileName", out var a) && a is string attachDbFileName && !string.IsNullOrEmpty(attachDbFileName))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlServer;
}
if (builder.TryGetValue("Initial Catalog", out var i) && i is string initialCatalog && !string.IsNullOrEmpty(initialCatalog))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlServer;
}
return Constants.DbProviderNames.SqlServer;
}
throw new ArgumentException("Cannot determine provider name from connection string", nameof(connectionString));
throw new ArgumentException("Cannot determine provider name from connection string",
nameof(connectionString));
}
}
}

View File

@@ -1,10 +1,10 @@

namespace Umbraco.Core.Dashboards
namespace Umbraco.Cms.Core.Configuration
{
public class ContentDashboardSettings
{
private const string DefaultContentDashboardPath = "cms";
/// <summary>
/// Gets a value indicating whether the content dashboard should be available to all users.
/// </summary>

View File

@@ -64,7 +64,7 @@ namespace Umbraco.Cms.Core.Configuration.Grid
}
// add manifest editors, skip duplicates
foreach (var gridEditor in _manifestParser.Manifest.GridEditors)
foreach (var gridEditor in _manifestParser.CombinedManifest.GridEditors)
{
if (editors.Contains(gridEditor) == false) editors.Add(gridEditor);
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.ComponentModel;
using System.Net;
namespace Umbraco.Cms.Core.Configuration.Models
{
/// <summary>
/// Typed configuration options for basic authentication settings.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigBasicAuth)]
public class BasicAuthSettings
{
private const bool StaticEnabled = false;
/// <summary>
/// Gets or sets a value indicating whether to keep the user logged in.
/// </summary>
[DefaultValue(StaticEnabled)]
public bool Enabled { get; set; } = StaticEnabled;
public string[] AllowedIPs { get; set; } = Array.Empty<string>();
}
}

View File

@@ -30,5 +30,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
/// </summary>
[DefaultValue(StaticSqlPageSize)]
public int SqlPageSize { get; set; } = StaticSqlPageSize;
public bool UnPublishedContentCompression { get; set; } = false;
}
}

View File

@@ -15,6 +15,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
internal const bool StaticHideDisabledUsersInBackOffice = false;
internal const bool StaticAllowPasswordReset = true;
internal const string StaticAuthCookieName = "UMB_UCONTEXT";
internal const string StaticAllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\";
/// <summary>
/// Gets or sets a value indicating whether to keep the user logged in.
@@ -50,6 +51,12 @@ namespace Umbraco.Cms.Core.Configuration.Models
/// </summary>
public bool UsernameIsEmail { get; set; } = true;
/// <summary>
/// Gets or sets the set of allowed characters for a username
/// </summary>
[DefaultValue(StaticAllowedUserNameCharacters)]
public string AllowedUserNameCharacters { get; set; } = StaticAllowedUserNameCharacters;
/// <summary>
/// Gets or sets a value for the user password settings.
/// </summary>

View File

@@ -46,6 +46,7 @@
public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification";
public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version";
public const string ConfigSecurity = ConfigPrefix + "Security";
public const string ConfigBasicAuth = ConfigPrefix + "BasicAuth";
public const string ConfigTours = ConfigPrefix + "Tours";
public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder";
public const string ConfigWebRouting = ConfigPrefix + "WebRouting";

View File

@@ -33,7 +33,7 @@ namespace Umbraco.Cms.Core.ContentApps
// its dependencies too, and that can create cycles or other oddities
var manifestParser = factory.GetRequiredService<IManifestParser>();
var ioHelper = factory.GetRequiredService<IIOHelper>();
return base.CreateItems(factory).Concat(manifestParser.Manifest.ContentApps.Select(x => new ManifestContentAppFactory(x, ioHelper)));
return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.ContentApps.Select(x => new ManifestContentAppFactory(x, ioHelper)));
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Dashboards
// its dependencies too, and that can create cycles or other oddities
var manifestParser = factory.GetRequiredService<IManifestParser>();
var dashboardSections = Merge(base.CreateItems(factory), manifestParser.Manifest.Dashboards);
var dashboardSections = Merge(base.CreateItems(factory), manifestParser.CombinedManifest.Dashboards);
return dashboardSections;
}

View File

@@ -71,6 +71,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
.AddUmbracoOptions<UmbracoPluginSettings>()
.AddUmbracoOptions<UnattendedSettings>()
.AddUmbracoOptions<RichTextEditorSettings>()
.AddUmbracoOptions<BasicAuthSettings>()
.AddUmbracoOptions<RuntimeMinificationSettings>();
return builder;

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Core.Semver;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Install.InstallSteps
{
/// <summary>
@@ -39,7 +39,7 @@ namespace Umbraco.Cms.Core.Install.InstallSteps
var currentState = FormatGuidState(_runtimeState.CurrentMigrationState);
var newState = FormatGuidState(_runtimeState.FinalMigrationState);
var newVersion = _umbracoVersion.SemanticVersion.ToString();
var newVersion = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
var oldVersion = new SemVersion(_umbracoVersion.SemanticVersion.Major, 0, 0).ToString(); //TODO can we find the old version somehow? e.g. from current state
var reportUrl = $"https://our.umbraco.com/contribute/releases/compare?from={oldVersion}&to={newVersion}&notes=1";

View File

@@ -0,0 +1,26 @@
namespace Umbraco.Cms.Core.Manifest
{
public enum BundleOptions
{
/// <summary>
/// The default bundling behavior for assets in the package folder.
/// </summary>
/// <remarks>
/// The assets will be bundled with the typical packages bundle.
/// </remarks>
Default = 0,
/// <summary>
/// The assets in the package will not be processed at all and will all be requested as individual assets.
/// </summary>
/// <remarks>
/// This will essentially be a bundle that has composite processing turned off for both debug and production.
/// </remarks>
None = 1,
/// <summary>
/// The packages assets will be processed as it's own separate bundle. (in debug, files will not be processed)
/// </summary>
Independent = 2
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Cms.Core.Manifest
{
/// <summary>
/// A package manifest made up of all combined manifests
/// </summary>
public class CompositePackageManifest
{
public CompositePackageManifest(
IReadOnlyList<IDataEditor> propertyEditors,
IReadOnlyList<IDataEditor> parameterEditors,
IReadOnlyList<GridEditor> gridEditors,
IReadOnlyList<ManifestContentAppDefinition> contentApps,
IReadOnlyList<ManifestDashboard> dashboards,
IReadOnlyList<ManifestSection> sections,
IReadOnlyDictionary<BundleOptions, IReadOnlyList<ManifestAssets>> scripts,
IReadOnlyDictionary<BundleOptions, IReadOnlyList<ManifestAssets>> stylesheets)
{
PropertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors));
ParameterEditors = parameterEditors ?? throw new ArgumentNullException(nameof(parameterEditors));
GridEditors = gridEditors ?? throw new ArgumentNullException(nameof(gridEditors));
ContentApps = contentApps ?? throw new ArgumentNullException(nameof(contentApps));
Dashboards = dashboards ?? throw new ArgumentNullException(nameof(dashboards));
Sections = sections ?? throw new ArgumentNullException(nameof(sections));
Scripts = scripts ?? throw new ArgumentNullException(nameof(scripts));
Stylesheets = stylesheets ?? throw new ArgumentNullException(nameof(stylesheets));
}
/// <summary>
/// Gets or sets the property editors listed in the manifest.
/// </summary>
public IReadOnlyList<IDataEditor> PropertyEditors { get; }
/// <summary>
/// Gets or sets the parameter editors listed in the manifest.
/// </summary>
public IReadOnlyList<IDataEditor> ParameterEditors { get; }
/// <summary>
/// Gets or sets the grid editors listed in the manifest.
/// </summary>
public IReadOnlyList<GridEditor> GridEditors { get; }
/// <summary>
/// Gets or sets the content apps listed in the manifest.
/// </summary>
public IReadOnlyList<ManifestContentAppDefinition> ContentApps { get; }
/// <summary>
/// Gets or sets the dashboards listed in the manifest.
/// </summary>
public IReadOnlyList<ManifestDashboard> Dashboards { get; }
/// <summary>
/// Gets or sets the sections listed in the manifest.
/// </summary>
public IReadOnlyCollection<ManifestSection> Sections { get; }
public IReadOnlyDictionary<BundleOptions, IReadOnlyList<ManifestAssets>> Scripts { get; }
public IReadOnlyDictionary<BundleOptions, IReadOnlyList<ManifestAssets>> Stylesheets { get; }
}
}

View File

@@ -4,13 +4,13 @@ namespace Umbraco.Cms.Core.Manifest
{
public interface IManifestParser
{
string Path { get; set; }
string AppPluginsPath { get; set; }
/// <summary>
/// Gets all manifests, merged into a single manifest object.
/// </summary>
/// <returns></returns>
PackageManifest Manifest { get; }
CompositePackageManifest CombinedManifest { get; }
/// <summary>
/// Parses a manifest.

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Cms.Core.Manifest
{
public class ManifestAssets
{
public ManifestAssets(string packageName, IReadOnlyList<string> assets)
{
PackageName = packageName ?? throw new ArgumentNullException(nameof(packageName));
Assets = assets ?? throw new ArgumentNullException(nameof(assets));
}
public string PackageName { get; }
public IReadOnlyList<string> Assets { get; }
}
}

View File

@@ -6,6 +6,7 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Manifest
{
/// <summary>
/// Represents the content of a package manifest.
/// </summary>
@@ -47,6 +48,8 @@ namespace Umbraco.Cms.Core.Manifest
/// </remarks>
[IgnoreDataMember]
public string Source { get; set; }
[DataMember(Name = "bundleOptions")]
public BundleOptions BundleOptions { get; set; }
/// <summary>
/// Gets or sets the scripts listed in the manifest.

View File

@@ -0,0 +1,27 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models.ContentEditing
{
/// <summary>
/// A model for retrieving multiple content types based on their keys.
/// </summary>
[DataContract(Name = "contentTypes", Namespace = "")]
public class ContentTypesByKeys
{
/// <summary>
/// ID of the parent of the content type.
/// </summary>
[DataMember(Name = "parentId")]
[Required]
public int ParentId { get; set; }
/// <summary>
/// The id of every content type to get.
/// </summary>
[DataMember(Name = "contentTypeKeys")]
[Required]
public Guid[] ContentTypeKeys { get; set; }
}
}

View File

@@ -7,5 +7,15 @@ namespace Umbraco.Cms.Core.Notifications
public SendEmailNotification(NotificationEmailModel message) => Message = message;
public NotificationEmailModel Message { get; }
/// <summary>
/// Call to tell Umbraco that the email sending is handled.
/// </summary>
public void HandleEmail() => IsHandled = true;
/// <summary>
/// Returns true if the email sending is handled.
/// </summary>
public bool IsHandled { get; private set; }
}
}

View File

@@ -9,7 +9,12 @@ namespace Umbraco.Cms.Core.PropertyEditors
///
/// </remarks>
public interface IPropertyCacheCompression
{
bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias);
{/// <summary>
/// Whether a property on the content is/should be compressed
/// </summary>
/// <param name="content">The content</param>
/// <param name="propertyTypeAlias">The property to compress or not</param>
/// <param name="published">Whether this content is the published version</param>
bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias, bool published);
}
}

View File

@@ -4,6 +4,13 @@ namespace Umbraco.Cms.Core.PropertyEditors
{
public interface IPropertyCacheCompressionOptions
{
bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor);
/// <summary>
/// Whether a property on the content is/should be compressed
/// </summary>
/// <param name="content">The content</param>
/// <param name="propertyType">The property to compress or not</param>
/// <param name="dataEditor">The datatype of the property to compress or not</param>
/// <param name="published">Whether this content is the published version</param>
bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published);
}
}

View File

@@ -12,6 +12,14 @@ namespace Umbraco.Cms.Core.PropertyEditors
public bool TryGetMediaPath(string propertyEditorAlias, object value, out string mediaPath)
{
// We can't get a media path from a null value
// The value will be null when uploading a brand new image, since we try to get the "old path" which doesn't exist yet.
if (value is null)
{
mediaPath = null;
return false;
}
foreach(IMediaUrlGenerator generator in this)
{
if (generator.TryGetMediaPath(propertyEditorAlias, value, out var mp))

View File

@@ -7,6 +7,6 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// </summary>
public sealed class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions
{
public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor) => false;
public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published) => false;
}
}

View File

@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
public ParameterEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser)
: base(() => dataEditors
.Where(x => (x.Type & EditorType.MacroParameter) > 0)
.Union(manifestParser.Manifest.PropertyEditors))
.Union(manifestParser.CombinedManifest.PropertyEditors))
{ }
// note: virtual so it can be mocked

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Core.PropertyEditors
namespace Umbraco.Cms.Core.PropertyEditors
{
/// <summary>
@@ -16,13 +16,13 @@ namespace Umbraco.Core.PropertyEditors
private readonly IPropertyCacheCompressionOptions _compressionOptions;
private readonly IReadOnlyDictionary<int, IContentTypeComposition> _contentTypes;
private readonly PropertyEditorCollection _propertyEditors;
private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias), bool> _isCompressedCache;
private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias, bool published), bool> _isCompressedCache;
public PropertyCacheCompression(
IPropertyCacheCompressionOptions compressionOptions,
IReadOnlyDictionary<int, IContentTypeComposition> contentTypes,
PropertyEditorCollection propertyEditors,
ConcurrentDictionary<(int, string), bool> compressedStoragePropertyEditorCache)
ConcurrentDictionary<(int, string, bool), bool> compressedStoragePropertyEditorCache)
{
_compressionOptions = compressionOptions;
_contentTypes = contentTypes ?? throw new System.ArgumentNullException(nameof(contentTypes));
@@ -30,9 +30,9 @@ namespace Umbraco.Core.PropertyEditors
_isCompressedCache = compressedStoragePropertyEditorCache;
}
public bool IsCompressed(IReadOnlyContentBase content, string alias)
public bool IsCompressed(IReadOnlyContentBase content, string alias, bool published)
{
var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias), x =>
var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias, published), x =>
{
if (!_contentTypes.TryGetValue(x.contentTypeId, out var ct))
return false;
@@ -44,7 +44,7 @@ namespace Umbraco.Core.PropertyEditors
if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out var propertyEditor))
return false;
return _compressionOptions.IsCompressed(content, propertyType, propertyEditor);
return _compressionOptions.IsCompressed(content, propertyType, propertyEditor, published);
});
return compressedStorage;

View File

@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser)
: base(() => dataEditors
.Where(x => (x.Type & EditorType.PropertyValue) > 0)
.Union(manifestParser.Manifest.PropertyEditors))
.Union(manifestParser.CombinedManifest.PropertyEditors))
{ }
public PropertyEditorCollection(DataEditorCollection dataEditors)

View File

@@ -0,0 +1,20 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.PropertyEditors
{
/// <summary>
/// Compress large, non published text properties
/// </summary>
public class UnPublishedContentPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions
{
public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published)
{
if (!published && propertyType.SupportsPublishing && propertyType.ValueStorageType == ValueStorageType.Ntext)
{
//Only compress non published content that supports publishing and the property is text
return true;
}
return false;
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Cms.Core.Sections
// its dependencies too, and that can create cycles or other oddities
var manifestParser = factory.GetRequiredService<IManifestParser>();
return base.CreateItems(factory).Concat(manifestParser.Manifest.Sections);
return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.Sections);
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Net;
namespace Umbraco.Cms.Core.Services
{
public interface IBasicAuthService
{
bool IsBasicAuthEnabled();
bool IsIpAllowListed(IPAddress clientIpAddress);
}
}

View File

@@ -34,17 +34,6 @@ namespace Umbraco.Extensions
public static string Localize(this ILocalizedTextService manager, string area, string alias, string[] tokens)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, ConvertToDictionaryVars(tokens));
/// <summary>
/// Localize using the current thread culture
/// </summary>
/// <param name="manager"></param>
/// <param name="area"></param>
/// <param name="alias"></param>
/// <param name="tokens"></param>
/// <returns></returns>
public static string Localize(this ILocalizedTextService manager, string area, string alias, IDictionary<string, string> tokens = null)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens);
/// <summary>
/// Localize a key without any variables
/// </summary>

View File

@@ -17,7 +17,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="5.0.7" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="5.0.8" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
@@ -59,4 +59,8 @@
<ItemGroup>
<EmbeddedResource Include="EmbeddedResources\**\*" />
</ItemGroup>
<ItemGroup>
<Folder Include="ContentEditing" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,44 @@
using System;
namespace Umbraco.Cms.Core.WebAssets
{
public struct BundlingOptions : IEquatable<BundlingOptions>
{
public static BundlingOptions OptimizedAndComposite => new BundlingOptions(true, true);
public static BundlingOptions OptimizedNotComposite => new BundlingOptions(true, false);
public static BundlingOptions NotOptimizedNotComposite => new BundlingOptions(false, false);
public static BundlingOptions NotOptimizedAndComposite => new BundlingOptions(false, true);
public BundlingOptions(bool optimizeOutput = true, bool enabledCompositeFiles = true)
{
OptimizeOutput = optimizeOutput;
EnabledCompositeFiles = enabledCompositeFiles;
}
/// <summary>
/// If true, the files in the bundle will be minified
/// </summary>
public bool OptimizeOutput { get; }
/// <summary>
/// If true, the files in the bundle will be combined, if false the files
/// will be served as individual files.
/// </summary>
public bool EnabledCompositeFiles { get; }
public override bool Equals(object obj) => obj is BundlingOptions options && Equals(options);
public bool Equals(BundlingOptions other) => OptimizeOutput == other.OptimizeOutput && EnabledCompositeFiles == other.EnabledCompositeFiles;
public override int GetHashCode()
{
int hashCode = 2130304063;
hashCode = hashCode * -1521134295 + OptimizeOutput.GetHashCode();
hashCode = hashCode * -1521134295 + EnabledCompositeFiles.GetHashCode();
return hashCode;
}
public static bool operator ==(BundlingOptions left, BundlingOptions right) => left.Equals(right);
public static bool operator !=(BundlingOptions left, BundlingOptions right) => !(left == right);
}
}

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Cms.Core.WebAssets
/// <exception cref="InvalidOperationException">
/// Thrown if any of the paths specified are not absolute
/// </exception>
void CreateCssBundle(string bundleName, bool optimizeOutput, params string[] filePaths);
void CreateCssBundle(string bundleName, BundlingOptions bundleOptions, params string[] filePaths);
/// <summary>
/// Renders the html link tag for the bundle
@@ -48,7 +48,7 @@ namespace Umbraco.Cms.Core.WebAssets
/// <exception cref="InvalidOperationException">
/// Thrown if any of the paths specified are not absolute
/// </exception>
void CreateJsBundle(string bundleName, bool optimizeOutput, params string[] filePaths);
void CreateJsBundle(string bundleName, BundlingOptions bundleOptions, params string[] filePaths);
/// <summary>
/// Renders the html script tag for the bundle

View File

@@ -30,11 +30,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SecurityCodeScan">
<Version>3.5.4</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Umbraco.Code" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@@ -2,12 +2,8 @@ using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Infrastructure.Search;
using Umbraco.Cms.Infrastructure.Sync;
using Umbraco.Extensions;
@@ -40,49 +36,67 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerRegistrar<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetServerRegistrar<T>(this IUmbracoBuilder builder)
where T : class, IServerRoleAccessor
=> builder.Services.AddUnique<IServerRoleAccessor, T>();
{
builder.Services.AddUnique<IServerRoleAccessor, T>();
return builder;
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, Func<IServiceProvider, IServerRoleAccessor> factory)
=> builder.Services.AddUnique(factory);
public static IUmbracoBuilder SetServerRegistrar(this IUmbracoBuilder builder, Func<IServiceProvider, IServerRoleAccessor> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRoleAccessor registrar)
=> builder.Services.AddUnique(registrar);
public static IUmbracoBuilder SetServerRegistrar(this IUmbracoBuilder builder, IServerRoleAccessor registrar)
{
builder.Services.AddUnique(registrar);
return builder;
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerMessenger<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetServerMessenger<T>(this IUmbracoBuilder builder)
where T : class, IServerMessenger
=> builder.Services.AddUnique<IServerMessenger, T>();
{
builder.Services.AddUnique<IServerMessenger, T>();
return builder;
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, Func<IServiceProvider, IServerMessenger> factory)
=> builder.Services.AddUnique(factory);
public static IUmbracoBuilder SetServerMessenger(this IUmbracoBuilder builder, Func<IServiceProvider, IServerMessenger> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar)
=> builder.Services.AddUnique(registrar);
public static IUmbracoBuilder SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar)
{
builder.Services.AddUnique(registrar);
return builder;
}
}
}

View File

@@ -40,6 +40,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique<IDomainService, DomainService>();
builder.Services.AddUnique<IAuditService, AuditService>();
builder.Services.AddUnique<ICacheInstructionService, CacheInstructionService>();
builder.Services.AddUnique<IBasicAuthService, BasicAuthService>();
builder.Services.AddUnique<ITagService, TagService>();
builder.Services.AddUnique<IContentService, ContentService>();
builder.Services.AddUnique<IUserService, UserService>();

View File

@@ -1,6 +1,5 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Dictionary;
using Umbraco.Cms.Core.IO;
@@ -21,10 +20,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetCultureDictionaryFactory<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetCultureDictionaryFactory<T>(this IUmbracoBuilder builder)
where T : class, ICultureDictionaryFactory
{
builder.Services.AddUnique<ICultureDictionaryFactory, T>();
return builder;
}
/// <summary>
@@ -32,9 +32,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a culture dictionary factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func<IServiceProvider, ICultureDictionaryFactory> factory)
public static IUmbracoBuilder SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func<IServiceProvider, ICultureDictionaryFactory> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -42,9 +43,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory)
public static IUmbracoBuilder SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -52,10 +54,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetPublishedContentModelFactory<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetPublishedContentModelFactory<T>(this IUmbracoBuilder builder)
where T : class, IPublishedModelFactory
{
builder.Services.AddUnique<IPublishedModelFactory, T>();
return builder;
}
/// <summary>
@@ -63,9 +66,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedModelFactory> factory)
public static IUmbracoBuilder SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedModelFactory> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -73,9 +77,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory)
public static IUmbracoBuilder SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -83,10 +88,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the short string helper.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetShortStringHelper<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetShortStringHelper<T>(this IUmbracoBuilder builder)
where T : class, IShortStringHelper
{
builder.Services.AddUnique<IShortStringHelper, T>();
return builder;
}
/// <summary>
@@ -94,9 +100,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, Func<IServiceProvider, IShortStringHelper> factory)
public static IUmbracoBuilder SetShortStringHelper(this IUmbracoBuilder builder, Func<IServiceProvider, IShortStringHelper> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -104,9 +111,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper)
public static IUmbracoBuilder SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper)
{
builder.Services.AddUnique(helper);
return builder;
}
/// <summary>
@@ -114,19 +122,23 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="filesystemFactory">Factory method to create an IFileSystem implementation used in the MediaFileManager</param>
public static void SetMediaFileSystem(this IUmbracoBuilder builder,
Func<IServiceProvider, IFileSystem> filesystemFactory) => builder.Services.AddUnique(
provider =>
{
IFileSystem filesystem = filesystemFactory(provider);
// We need to use the Filesystems to create a shadow wrapper,
// because shadow wrapper requires the IsScoped delegate from the FileSystems.
// This is used by the scope provider when taking control of the filesystems.
FileSystems fileSystems = provider.GetRequiredService<FileSystems>();
IFileSystem shadow = fileSystems.CreateShadowWrapper(filesystem, "media");
public static IUmbracoBuilder SetMediaFileSystem(this IUmbracoBuilder builder,
Func<IServiceProvider, IFileSystem> filesystemFactory)
{
builder.Services.AddUnique(
provider =>
{
IFileSystem filesystem = filesystemFactory(provider);
// We need to use the Filesystems to create a shadow wrapper,
// because shadow wrapper requires the IsScoped delegate from the FileSystems.
// This is used by the scope provider when taking control of the filesystems.
FileSystems fileSystems = provider.GetRequiredService<FileSystems>();
IFileSystem shadow = fileSystems.CreateShadowWrapper(filesystem, "media");
return provider.CreateInstance<MediaFileManager>(shadow);
});
return provider.CreateInstance<MediaFileManager>(shadow);
});
return builder;
}
/// <summary>
/// Register FileSystems with a method to configure the <see cref="FileSystems"/>.
@@ -135,7 +147,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// <param name="configure">Method that configures the <see cref="FileSystems"/>.</param>
/// <exception cref="ArgumentNullException">Throws exception if <paramref name="configure"/> is null.</exception>
/// <exception cref="InvalidOperationException">Throws exception if full path can't be resolved successfully.</exception>
public static void ConfigureFileSystems(this IUmbracoBuilder builder,
public static IUmbracoBuilder ConfigureFileSystems(this IUmbracoBuilder builder,
Action<IServiceProvider, FileSystems> configure)
{
if (configure == null)
@@ -150,6 +162,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
configure(provider, fileSystems);
return fileSystems;
});
return builder;
}
/// <summary>
@@ -157,10 +170,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the log viewer.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetLogViewer<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetLogViewer<T>(this IUmbracoBuilder builder)
where T : class, ILogViewer
{
builder.Services.AddUnique<ILogViewer, T>();
return builder;
}
/// <summary>
@@ -168,19 +182,21 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, Func<IServiceProvider, ILogViewer> factory)
public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder, Func<IServiceProvider, ILogViewer> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
/// <param name="viewer">A log viewer.</param>
public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
{
builder.Services.AddUnique(viewer);
return builder;
}
}
}

View File

@@ -3,6 +3,7 @@
using System.Net.Mail;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
@@ -23,12 +24,14 @@ namespace Umbraco.Cms.Infrastructure
private readonly IEventAggregator _eventAggregator;
private readonly GlobalSettings _globalSettings;
private readonly bool _notificationHandlerRegistered;
private readonly ILogger<EmailSender> _logger;
public EmailSender(IOptions<GlobalSettings> globalSettings, IEventAggregator eventAggregator)
public EmailSender(
ILogger<EmailSender> logger,
IOptions<GlobalSettings> globalSettings,
IEventAggregator eventAggregator)
: this(globalSettings, eventAggregator, null)
{
}
=> _logger = logger;
public EmailSender(IOptions<GlobalSettings> globalSettings, IEventAggregator eventAggregator, INotificationHandler<SendEmailNotification> handler)
{
@@ -49,39 +52,47 @@ namespace Umbraco.Cms.Infrastructure
private async Task SendAsyncInternal(EmailMessage message, bool enableNotification)
{
if (enableNotification)
{
var notification = new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp?.From));
await _eventAggregator.PublishAsync(notification);
// if a handler handled sending the email then don't continue.
if (notification.IsHandled)
{
_logger.LogDebug("The email sending for {Subject} was handled by a notification handler", notification.Message.Subject);
return;
}
}
if (_globalSettings.IsSmtpServerConfigured == false)
{
if (enableNotification)
{
await _eventAggregator.PublishAsync(
new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp?.From)));
}
_logger.LogDebug("Could not send email for {Subject}. It was not handled by a notification handler and there is no SMTP configured.", message.Subject);
return;
}
using (var client = new SmtpClient())
using var client = new SmtpClient();
await client.ConnectAsync(_globalSettings.Smtp.Host,
_globalSettings.Smtp.Port,
(MailKit.Security.SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
if (!(_globalSettings.Smtp.Username is null && _globalSettings.Smtp.Password is null))
{
await client.ConnectAsync(_globalSettings.Smtp.Host,
_globalSettings.Smtp.Port,
(MailKit.Security.SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
if (!(_globalSettings.Smtp.Username is null && _globalSettings.Smtp.Password is null))
{
await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
}
var mailMessage = message.ToMimeMessage(_globalSettings.Smtp.From);
if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network)
{
await client.SendAsync(mailMessage);
}
else
{
client.Send(mailMessage);
}
await client.DisconnectAsync(true);
await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
}
var mailMessage = message.ToMimeMessage(_globalSettings.Smtp.From);
if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network)
{
await client.SendAsync(mailMessage);
}
else
{
client.Send(mailMessage);
}
await client.DisconnectAsync(true);
}
/// <summary>

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using System;
using Microsoft.Extensions.Configuration;
using Serilog;
using Serilog.Events;
@@ -15,17 +14,6 @@ namespace Umbraco.Cms.Core.Logging.Serilog
{
public global::Serilog.ILogger SerilogLog { get; }
/// <summary>
/// Initialize a new instance of the <see cref="SerilogLogger"/> class with a configuration file.
/// </summary>
/// <param name="logConfigFile"></param>
public SerilogLogger(FileInfo logConfigFile)
{
SerilogLog = new LoggerConfiguration()
.ReadFrom.AppSettings(filePath: logConfigFile.FullName)
.CreateLogger();
}
public SerilogLogger(LoggerConfiguration logConfig)
{
//Configure Serilog static global logger with config passed in

View File

@@ -62,22 +62,22 @@ namespace Umbraco.Cms.Core.Manifest
/// <summary>
/// Initializes a new instance of the <see cref="ManifestParser"/> class.
/// </summary>
private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string path, ILogger<ManifestParser> logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment)
private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string appPluginsPath, ILogger<ManifestParser> logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment)
{
if (appCaches == null) throw new ArgumentNullException(nameof(appCaches));
_cache = appCaches.RuntimeCache;
_validators = validators ?? throw new ArgumentNullException(nameof(validators));
_filters = filters ?? throw new ArgumentNullException(nameof(filters));
if (path == null) throw new ArgumentNullException(nameof(path));
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(path));
if (appPluginsPath == null) throw new ArgumentNullException(nameof(appPluginsPath));
if (string.IsNullOrWhiteSpace(appPluginsPath)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(appPluginsPath));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_ioHelper = ioHelper;
_hostingEnvironment = hostingEnvironment;
Path = path;
AppPluginsPath = appPluginsPath;
}
public string Path
public string AppPluginsPath
{
get => _path;
set => _path = value.StartsWith("~/") ? _hostingEnvironment.MapPathContentRoot(value) : value;
@@ -87,11 +87,12 @@ namespace Umbraco.Cms.Core.Manifest
/// Gets all manifests, merged into a single manifest object.
/// </summary>
/// <returns></returns>
public PackageManifest Manifest
=> _cache.GetCacheItem<PackageManifest>("Umbraco.Core.Manifest.ManifestParser::Manifests", () =>
public CompositePackageManifest CombinedManifest
=> _cache.GetCacheItem<CompositePackageManifest>("Umbraco.Core.Manifest.ManifestParser::Manifests", () =>
{
var manifests = GetManifests();
IEnumerable<PackageManifest> manifests = GetManifests();
return MergeManifests(manifests);
}, new TimeSpan(0, 4, 0));
/// <summary>
@@ -130,10 +131,10 @@ namespace Umbraco.Cms.Core.Manifest
/// <summary>
/// Merges all manifests into one.
/// </summary>
private static PackageManifest MergeManifests(IEnumerable<PackageManifest> manifests)
private static CompositePackageManifest MergeManifests(IEnumerable<PackageManifest> manifests)
{
var scripts = new HashSet<string>();
var stylesheets = new HashSet<string>();
var scripts = new Dictionary<BundleOptions, List<ManifestAssets>>();
var stylesheets = new Dictionary<BundleOptions, List<ManifestAssets>>();
var propertyEditors = new List<IDataEditor>();
var parameterEditors = new List<IDataEditor>();
var gridEditors = new List<GridEditor>();
@@ -141,10 +142,28 @@ namespace Umbraco.Cms.Core.Manifest
var dashboards = new List<ManifestDashboard>();
var sections = new List<ManifestSection>();
foreach (var manifest in manifests)
foreach (PackageManifest manifest in manifests)
{
if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script);
if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet);
if (manifest.Scripts != null)
{
if (!scripts.TryGetValue(manifest.BundleOptions, out List<ManifestAssets> scriptsPerBundleOption))
{
scriptsPerBundleOption = new List<ManifestAssets>();
scripts[manifest.BundleOptions] = scriptsPerBundleOption;
}
scriptsPerBundleOption.Add(new ManifestAssets(manifest.PackageName, manifest.Scripts));
}
if (manifest.Stylesheets != null)
{
if (!stylesheets.TryGetValue(manifest.BundleOptions, out List<ManifestAssets> stylesPerBundleOption))
{
stylesPerBundleOption = new List<ManifestAssets>();
stylesheets[manifest.BundleOptions] = stylesPerBundleOption;
}
stylesPerBundleOption.Add(new ManifestAssets(manifest.PackageName, manifest.Stylesheets));
}
if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors);
if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors);
if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors);
@@ -153,17 +172,15 @@ namespace Umbraco.Cms.Core.Manifest
if (manifest.Sections != null) sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias.ToLowerInvariant()));
}
return new PackageManifest
{
Scripts = scripts.ToArray(),
Stylesheets = stylesheets.ToArray(),
PropertyEditors = propertyEditors.ToArray(),
ParameterEditors = parameterEditors.ToArray(),
GridEditors = gridEditors.ToArray(),
ContentApps = contentApps.ToArray(),
Dashboards = dashboards.ToArray(),
Sections = sections.ToArray()
};
return new CompositePackageManifest(
propertyEditors,
parameterEditors,
gridEditors,
contentApps,
dashboards,
sections,
scripts.ToDictionary(x => x.Key, x => (IReadOnlyList<ManifestAssets>)x.Value),
stylesheets.ToDictionary(x => x.Key, x => (IReadOnlyList<ManifestAssets>)x.Value));
}
// gets all manifest files (recursively)

View File

@@ -9,6 +9,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Table
public class CreateTableOfDtoBuilder : IExecutableBuilder
{
private readonly IMigrationContext _context;
// TODO: This doesn't do anything.
private readonly DatabaseType[] _supportedDatabaseTypes;
public CreateTableOfDtoBuilder(IMigrationContext context, params DatabaseType[] supportedDatabaseTypes)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
@@ -151,7 +151,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
//New UDI pickers with newer Ids
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picke (legacy)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker (legacy)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker (legacy)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Multi URL Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });

View File

@@ -211,8 +211,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
Merge()
.To<AddCmsContentNuByteColumn>("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}")
.To<UpgradedIncludeIndexes>("{4695D0C9-0729-4976-985B-048D503665D8}")
// to 9.0.0
.To<UpdateCmsPropertyGroupIdSeed>("{5C424554-A32D-4852-8ED1-A13508187901}")
// to 9.0.0 RC
.With()
.To<MigrateLogViewerQueriesFromFileToDb>("{22D801BA-A1FF-4539-BFCC-2139B55594F8}")
.To<ExternalLoginTableIndexes>("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}")
@@ -222,6 +222,17 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
//FINAL
.As("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}");
// TO 9.0.0
// This should be safe to execute again. We need it with a new name to ensure updates from all the following has executed this step.
// - 8.15 RC - Current state: {4695D0C9-0729-4976-985B-048D503665D8}
// - 8.15 Final - Current state: {5C424554-A32D-4852-8ED1-A13508187901}
// - 9.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12}
To<UpdateCmsPropertyGroupIdSeed>("{622E5172-42E1-4662-AD80-9504AF5A4E53}");
To<ExternalLoginTableIndexesFixup>("{10F7BB61-C550-426B-830B-7F954F689CDF}");
}
}
}

View File

@@ -1,6 +1,9 @@
using NPoco;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
{
@@ -14,12 +17,46 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
protected override void Migrate()
{
// allow null for the `data` field
if (DatabaseType.IsSqlCe())
{
// SQLCE does not support altering NTEXT, so we have to jump through some hoops to do it
// All column ordering must remain the same as what is defined in the DTO so we need to create a temp table,
// drop orig and then re-create/copy.
Create.Table<ContentNuDtoTemp>(withoutKeysAndIndexes: true).Do();
Execute.Sql($"INSERT INTO [{TempTableName}] SELECT nodeId, published, data, rv FROM [{Constants.DatabaseSchema.Tables.NodeData}]").Do();
Delete.Table(Constants.DatabaseSchema.Tables.NodeData).Do();
Create.Table<ContentNuDto>().Do();
Execute.Sql($"INSERT INTO [{Constants.DatabaseSchema.Tables.NodeData}] SELECT nodeId, published, data, rv, NULL FROM [{TempTableName}]").Do();
}
else
{
AlterColumn<ContentNuDto>(Constants.DatabaseSchema.Tables.NodeData, "data");
}
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
AddColumnIfNotExists<ContentNuDto>(columns, "dataRaw");
}
// allow null
AlterColumn<ContentNuDto>(Constants.DatabaseSchema.Tables.NodeData, "data");
private const string TempTableName = Constants.DatabaseSchema.TableNamePrefix + "cms" + "ContentNuTEMP";
[TableName(TempTableName)]
[ExplicitColumns]
private class ContentNuDtoTemp
{
[Column("nodeId")]
public int NodeId { get; set; }
[Column("published")]
public bool Published { get; set; }
[Column("data")]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[NullSetting(NullSetting = NullSettings.Null)]
public string Data { get; set; }
[Column("rv")]
public long Rv { get; set; }
}
}
}

View File

@@ -0,0 +1,19 @@
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
{
public class UpdateCmsPropertyGroupIdSeed : MigrationBase
{
public UpdateCmsPropertyGroupIdSeed(IMigrationContext context) : base(context)
{
}
protected override void Migrate()
{
if (DatabaseType.IsSqlCe())
{
Database.Execute(Sql("ALTER TABLE [cmsPropertyTypeGroup] ALTER COLUMN [id] IDENTITY (56,1)"));
}
}
}
}

View File

@@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
{
public class ExternalLoginTableIndexes : MigrationBase
{
public ExternalLoginTableIndexes(IMigrationContext context)

View File

@@ -0,0 +1,59 @@
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
{
/// <summary>
/// Fixes up the original <see cref="ExternalLoginTableIndexes"/> for post RC release to ensure that
/// the correct indexes are applied.
/// </summary>
public class ExternalLoginTableIndexesFixup : MigrationBase
{
public ExternalLoginTableIndexesFixup(IMigrationContext context) : base(context)
{
}
protected override void Migrate()
{
var indexName1 = "IX_" + ExternalLoginDto.TableName + "_LoginProvider";
var indexName2 = "IX_" + ExternalLoginDto.TableName + "_ProviderKey";
if (IndexExists(indexName1))
{
// drop it since the previous migration index was wrong, and we
// need to modify a column that belons to it
Delete.Index(indexName1).OnTable(ExternalLoginDto.TableName).Do();
}
if (IndexExists(indexName2))
{
// drop since it's using a column we're about to modify
Delete.Index(indexName2).OnTable(ExternalLoginDto.TableName).Do();
}
// then fixup the length of the loginProvider column
AlterColumn<ExternalLoginDto>(ExternalLoginDto.TableName, "loginProvider");
// create it with the correct definition
Create
.Index(indexName1)
.OnTable(ExternalLoginDto.TableName)
.OnColumn("loginProvider").Ascending()
.OnColumn("userId").Ascending()
.WithOptions()
.Unique()
.WithOptions()
.NonClustered()
.Do();
// re-create the original
Create
.Index(indexName2)
.OnTable(ExternalLoginDto.TableName)
.OnColumn("loginProvider").Ascending()
.OnColumn("providerKey").Ascending()
.WithOptions()
.NonClustered()
.Do();
}
}
}

View File

@@ -1,7 +1,7 @@
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Core.Models
namespace Umbraco.Cms.Core.Models
{
/// <summary>
/// Represents a media item with local crops.

View File

@@ -98,6 +98,13 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder.Embedded\", \"{1}\")]\n", tabs, ApiVersion.Current.Version);
}
// writes an attribute that specifies that an output may be null.
// (useful for consuming projects with nullable reference types enabled)
private static void WriteMaybeNullAttribute(StringBuilder sb, string tabs, bool isReturn = false)
{
sb.AppendFormat("{0}[{1}global::System.Diagnostics.CodeAnalysis.MaybeNull]\n", tabs, isReturn ? "return: " : "");
}
private void WriteContentType(StringBuilder sb, TypeModel type)
{
string sep;
@@ -185,9 +192,11 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
itemType);
WriteGeneratedCodeAttribute(sb, "\t\t");
WriteMaybeNullAttribute(sb, "\t\t", true);
sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor)\n");
sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias);\n");
WriteGeneratedCodeAttribute(sb, "\t\t");
WriteMaybeNullAttribute(sb, "\t\t", true);
sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<{0}, TValue>> selector)\n",
type.ClrName);
sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector);\n");
@@ -305,6 +314,8 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
}
WriteGeneratedCodeAttribute(sb, "\t\t");
if (!property.ModelClrType.IsValueType)
WriteMaybeNullAttribute(sb, "\t\t");
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
if (mixinStatic)
@@ -349,6 +360,8 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));
WriteGeneratedCodeAttribute(sb, "\t\t");
if (!property.ModelClrType.IsValueType)
WriteMaybeNullAttribute(sb, "\t\t", true);
sb.Append("\t\tpublic static ");
WriteClrType(sb, property.ClrTypeName);
sb.AppendFormat(" {0}(I{1} that, IPublishedValueFallback publishedValueFallback) => that.Value",
@@ -404,6 +417,9 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
if (!string.IsNullOrWhiteSpace(property.Name))
sb.AppendFormat("\t\t/// <summary>{0}</summary>\n", XmlCommentString(property.Name));
WriteGeneratedCodeAttribute(sb, "\t\t");
if (!property.ModelClrType.IsValueType)
WriteMaybeNullAttribute(sb, "\t\t");
sb.Append("\t\t");
WriteClrType(sb, property.ClrTypeName);
sb.AppendFormat(" {0} {{ get; }}\n",

View File

@@ -13,10 +13,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public int BulkInsertRecords<T>(IUmbracoDatabase database, IEnumerable<T> records)
{
var recordsA = records.ToArray();
if (recordsA.Length == 0) return 0;
if (!records.Any()) return 0;
return BulkInsertRecordsWithCommands(database, recordsA);
return BulkInsertRecordsWithCommands(database, records.ToArray());
}
/// <summary>

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
{
/// <summary>
/// Allows for specifying custom DB types that are not natively mapped.
/// </summary>
public struct SpecialDbType : IEquatable<SpecialDbType>
{
private readonly string _dbType;
public SpecialDbType(string dbType)
{
if (string.IsNullOrWhiteSpace(dbType))
{
throw new ArgumentException($"'{nameof(dbType)}' cannot be null or whitespace.", nameof(dbType));
}
_dbType = dbType;
}
public SpecialDbType(SpecialDbTypes specialDbTypes)
=> _dbType = specialDbTypes.ToString();
public static SpecialDbType NTEXT { get; } = new SpecialDbType(SpecialDbTypes.NTEXT);
public static SpecialDbType NCHAR { get; } = new SpecialDbType(SpecialDbTypes.NCHAR);
public static SpecialDbType NVARCHARMAX { get; } = new SpecialDbType(SpecialDbTypes.NVARCHARMAX);
public override bool Equals(object obj) => obj is SpecialDbType types && Equals(types);
public bool Equals(SpecialDbType other) => _dbType == other._dbType;
public override int GetHashCode() => 1038481724 + EqualityComparer<string>.Default.GetHashCode(_dbType);
public override string ToString() => _dbType.ToString();
// Make this directly castable to string
public static implicit operator string(SpecialDbType dbType) => dbType.ToString();
// direct equality operators with SpecialDbTypes enum
public static bool operator ==(SpecialDbTypes x, SpecialDbType y) => x.ToString() == y;
public static bool operator !=(SpecialDbTypes x, SpecialDbType y) => x.ToString() != y;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
{
@@ -12,13 +12,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
public class SpecialDbTypeAttribute : Attribute
{
public SpecialDbTypeAttribute(SpecialDbTypes databaseType)
{
DatabaseType = databaseType;
}
=> DatabaseType = new SpecialDbType(databaseType);
public SpecialDbTypeAttribute(string databaseType)
=> DatabaseType = new SpecialDbType(databaseType);
/// <summary>
/// Gets or sets the <see cref="SpecialDbTypes"/> for this column
/// Gets or sets the <see cref="SpecialDbType"/> for this column
/// </summary>
public SpecialDbTypes DatabaseType { get; private set; }
public SpecialDbType DatabaseType { get; private set; }
}
}

View File

@@ -1,13 +1,12 @@
namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
{
/// <summary>
/// Enum with the two special types that has to be supported because
/// of the current umbraco db schema.
/// Known special DB types required for Umbraco.
/// </summary>
public enum SpecialDbTypes
{
NTEXT,
NCHAR,
NVARCHARMAX
NVARCHARMAX,
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Data;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
@@ -12,9 +12,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions
//When DbType isn't set explicitly the Type will be used to find the right DbType in the SqlSyntaxProvider.
//This type is typically used as part of an initial table creation
public Type PropertyType { get; set; }
//Only used for special cases as part of an initial table creation
public bool HasSpecialDbType { get; set; }
public SpecialDbTypes DbType { get; set; }
/// <summary>
/// Used for column types that cannot be natively mapped.
/// </summary>
public SpecialDbType? CustomDbType { get; set; }
public virtual int Seeding { get; set; }
public virtual int Size { get; set; }
public virtual int Precision { get; set; }

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Reflection;
using NPoco;
@@ -75,8 +75,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions
var databaseTypeAttribute = propertyInfo.FirstAttribute<SpecialDbTypeAttribute>();
if (databaseTypeAttribute != null)
{
definition.HasSpecialDbType = true;
definition.DbType = databaseTypeAttribute.DatabaseType;
definition.CustomDbType = databaseTypeAttribute.DatabaseType;
}
else
{

View File

@@ -24,15 +24,16 @@ namespace Umbraco.Cms.Infrastructure.Persistence
IEnumerable<IProviderSpecificMapperFactory> providerSpecificMapperFactories)
{
_getFactory = getFactory;
_embeddedDatabaseCreators = embeddedDatabaseCreators.ToDictionary(x=>x.ProviderName);
_syntaxProviders = syntaxProviders.ToDictionary(x=>x.ProviderName);
_bulkSqlInsertProviders = bulkSqlInsertProviders.ToDictionary(x=>x.ProviderName);
_providerSpecificMapperFactories = providerSpecificMapperFactories.ToDictionary(x=>x.ProviderName);
_embeddedDatabaseCreators = embeddedDatabaseCreators.ToDictionary(x => x.ProviderName);
_syntaxProviders = syntaxProviders.ToDictionary(x => x.ProviderName);
_bulkSqlInsertProviders = bulkSqlInsertProviders.ToDictionary(x => x.ProviderName);
_providerSpecificMapperFactories = providerSpecificMapperFactories.ToDictionary(x => x.ProviderName);
}
public DbProviderFactory CreateFactory(string providerName)
{
if (string.IsNullOrEmpty(providerName)) return null;
if (string.IsNullOrEmpty(providerName))
return null;
return _getFactory(providerName);
}
@@ -40,7 +41,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName)
{
if(!_syntaxProviders.TryGetValue(providerName, out var result))
if (!_syntaxProviders.TryGetValue(providerName, out var result))
{
throw new InvalidOperationException($"Unknown provider name \"{providerName}\"");
}
@@ -51,7 +52,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public IBulkSqlInsertProvider CreateBulkSqlInsertProvider(string providerName)
{
if(!_bulkSqlInsertProviders.TryGetValue(providerName, out var result))
if (!_bulkSqlInsertProviders.TryGetValue(providerName, out var result))
{
return new BasicBulkSqlInsertProvider();
}
@@ -61,7 +62,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public void CreateDatabase(string providerName)
{
if(_embeddedDatabaseCreators.TryGetValue(providerName, out var creator))
if (_embeddedDatabaseCreators.TryGetValue(providerName, out var creator))
{
creator.Create();
}
@@ -69,7 +70,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public NPocoMapperCollection ProviderSpecificMappers(string providerName)
{
if(_providerSpecificMapperFactories.TryGetValue(providerName, out var mapperFactory))
if (_providerSpecificMapperFactories.TryGetValue(providerName, out var mapperFactory))
{
return mapperFactory.Mappers;
}

View File

@@ -24,12 +24,18 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
[Index(IndexTypes.NonClustered)]
public int UserId { get; set; }
/// <summary>
/// Used to store the name of the provider (i.e. Facebook, Google)
/// </summary>
[Column("loginProvider")]
[Length(4000)] // TODO: This value seems WAY too high, this is just a name
[Length(400)]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_LoginProvider")]
[Index(IndexTypes.UniqueNonClustered, ForColumns = "loginProvider,userId", Name = "IX_" + TableName + "_LoginProvider")]
public string LoginProvider { get; set; }
/// <summary>
/// Stores the key the provider uses to lookup the login
/// </summary>
[Column("providerKey")]
[Length(4000)]
[NullSetting(NullSetting = NullSettings.NotNull)]

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
internal class PropertyTypeGroupDto
{
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = 12)]
[PrimaryKeyColumn(IdentitySeed = 56)]
public int Id { get; set; }
[Column("contenttypeNodeId")]

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
@@ -6,6 +6,7 @@ using NPoco;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Persistence
{
@@ -40,9 +41,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence
_tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider);
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.Type);
// only real columns, exclude result columns
// only real columns, exclude result/computed columns
// Like NPoco does: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L59
_readerColumns = pd.Columns
.Where(x => x.Value.ResultColumn == false)
.Where(x => x.Value.ResultColumn == false && x.Value.ComputedColumn == false)
.Select(x => x.Value)
.ToArray();
@@ -68,22 +70,22 @@ namespace Umbraco.Cms.Infrastructure.Persistence
foreach (var col in _columnDefinitions)
{
SqlDbType sqlDbType;
if (col.HasSpecialDbType)
if (col.CustomDbType.HasValue)
{
//get the SqlDbType from the 'special type'
switch (col.DbType)
switch (col.CustomDbType)
{
case SpecialDbTypes.NTEXT:
case var x when x == SpecialDbType.NTEXT:
sqlDbType = SqlDbType.NText;
break;
case SpecialDbTypes.NCHAR:
case var x when x == SpecialDbType.NCHAR:
sqlDbType = SqlDbType.NChar;
break;
case SpecialDbTypes.NVARCHARMAX:
case var x when x == SpecialDbType.NVARCHARMAX:
sqlDbType = SqlDbType.NVarChar;
break;
default:
throw new ArgumentOutOfRangeException();
throw new ArgumentOutOfRangeException("The custom DB type " + col.CustomDbType + " is not supported for bulk import statements.");
}
}
else if (col.Type.HasValue)

View File

@@ -432,11 +432,12 @@ ORDER BY colName";
{
var list = new List<string>
{
"DELETE FROM umbracoUser2UserGroup WHERE userId = @id",
"DELETE FROM umbracoUser2NodeNotify WHERE userId = @id",
"DELETE FROM umbracoUserStartNode WHERE userId = @id",
"DELETE FROM umbracoUser WHERE id = @id",
"DELETE FROM umbracoExternalLogin WHERE id = @id"
$"DELETE FROM {Constants.DatabaseSchema.Tables.UserLogin} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.User2UserGroup} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.User2NodeNotify} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.UserStartNode} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.User} WHERE id = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.ExternalLogin} WHERE id = @id"
};
return list;
}

View File

@@ -39,6 +39,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence
/// <returns>The number of records that were inserted.</returns>
private int BulkInsertRecordsSqlServer<T>(IUmbracoDatabase database, PocoData pocoData, IEnumerable<T> records)
{
// TODO: The main reason this exists is because the NPoco InsertBulk method doesn't return the number of items.
// It is worth investigating the performance of this vs NPoco's because we use a custom BulkDataReader
// which in theory should be more efficient than NPocos way of building up an in-memory DataTable.
// create command against the original database.Connection
using (var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty))
{
@@ -50,7 +54,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence
var syntax = database.SqlContext.SqlSyntax as SqlServerSyntaxProvider;
if (syntax == null) throw new NotSupportedException("SqlSyntax must be SqlServerSyntaxProvider.");
using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName })
using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction)
{
BulkCopyTimeout = 0, // 0 = no bulk copy timeout. If a timeout occurs it will be an connection/command timeout.
DestinationTableName = tableName,
// be consistent with NPoco: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L50
BatchSize = 4096
})
using (var bulkReader = new PocoDataDataReader<T, SqlServerSyntaxProvider>(records, pocoData, syntax))
{
//we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
@@ -6,24 +6,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
{
public class DbTypes
{
public DbType DbType;
public string TextDefinition;
public bool ShouldQuoteValue;
public Dictionary<Type, string> ColumnTypeMap = new Dictionary<Type, string>();
public Dictionary<Type, DbType> ColumnDbTypeMap = new Dictionary<Type, DbType>();
public void Set<T>(DbType dbType, string fieldDefinition)
public DbTypes(IReadOnlyDictionary<Type, string> columnTypeMap, IReadOnlyDictionary<Type, DbType> columnDbTypeMap)
{
DbType = dbType;
TextDefinition = fieldDefinition;
ShouldQuoteValue = fieldDefinition != "INTEGER"
&& fieldDefinition != "BIGINT"
&& fieldDefinition != "DOUBLE"
&& fieldDefinition != "DECIMAL"
&& fieldDefinition != "BOOL";
ColumnTypeMap[typeof(T)] = fieldDefinition;
ColumnDbTypeMap[typeof(T)] = dbType;
ColumnTypeMap = columnTypeMap;
ColumnDbTypeMap = columnDbTypeMap;
}
public IReadOnlyDictionary<Type, string> ColumnTypeMap { get; }
public IReadOnlyDictionary<Type, DbType> ColumnDbTypeMap { get; }
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Data;
namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
{
internal class DbTypesFactory
{
private readonly Dictionary<Type, string> _columnTypeMap = new Dictionary<Type, string>();
private readonly Dictionary<Type, DbType> _columnDbTypeMap = new Dictionary<Type, DbType>();
public void Set<T>(DbType dbType, string fieldDefinition)
{
_columnTypeMap[typeof(T)] = fieldDefinition;
_columnDbTypeMap[typeof(T)] = dbType;
}
public DbTypes Create() => new DbTypes(_columnTypeMap, _columnDbTypeMap);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text.RegularExpressions;
@@ -28,7 +28,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
string GetQuotedName(string name);
bool DoesTableExist(IDatabase db, string tableName);
string GetIndexType(IndexTypes indexTypes);
string GetSpecialDbType(SpecialDbTypes dbTypes);
string GetSpecialDbType(SpecialDbType dbType);
string CreateTable { get; }
string DropTable { get; }
string AddColumn { get; }

View File

@@ -22,8 +22,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
DecimalColumnDefinition = "DECIMAL(38,6)";
TimeColumnDefinition = "TIME"; //SQLSERVER 2008+
BlobColumnDefinition = "VARBINARY(MAX)";
InitColumnTypeMap();
}
public override string RenameTable => "sp_rename '{0}', '{1}'";
@@ -78,7 +76,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
/// <returns></returns>
public virtual SqlDbType GetSqlDbType(Type clrType)
{
var dbType = DbTypeMap.ColumnDbTypeMap.First(x => x.Key == clrType).Value;
var dbType = DbTypeMap.ColumnDbTypeMap[clrType];
return GetSqlDbType(dbType);
}

View File

@@ -24,6 +24,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
public abstract class SqlSyntaxProviderBase<TSyntax> : ISqlSyntaxProvider
where TSyntax : ISqlSyntaxProvider
{
private readonly Lazy<DbTypes> _dbTypes;
protected SqlSyntaxProviderBase()
{
ClauseOrder = new List<Func<ColumnDefinition, string>>
@@ -42,94 +44,96 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength);
DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale);
InitColumnTypeMap();
// ReSharper disable VirtualMemberCallInConstructor
// ok to call virtual GetQuotedXxxName here - they don't depend on any state
var col = Regex.Escape(GetQuotedColumnName("column")).Replace("column", @"\w+");
var fld = Regex.Escape(GetQuotedTableName("table") + ".").Replace("table", @"\w+") + col;
// ReSharper restore VirtualMemberCallInConstructor
AliasRegex = new Regex("(" + fld + @")\s+AS\s+(" + col + ")", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled);
_dbTypes = new Lazy<DbTypes>(InitColumnTypeMap);
}
public Regex AliasRegex { get; }
public string GetWildcardPlaceholder()
{
return "%";
}
public string GetWildcardPlaceholder() => "%";
public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})";
public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})";
public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})";
public string StringLengthNonUnicodeColumnDefinitionFormat { get; } = "VARCHAR({0})";
public string StringLengthUnicodeColumnDefinitionFormat { get; } = "NVARCHAR({0})";
public string DecimalColumnDefinitionFormat { get; } = "DECIMAL({0},{1})";
public string DefaultValueFormat = "DEFAULT ({0})";
public int DefaultStringLength = 255;
public int DefaultDecimalPrecision = 20;
public int DefaultDecimalScale = 9;
public string DefaultValueFormat { get; } = "DEFAULT ({0})";
public int DefaultStringLength { get; } = 255;
public int DefaultDecimalPrecision { get; } = 20;
public int DefaultDecimalScale { get; } = 9;
//Set by Constructor
public string StringColumnDefinition;
public string StringLengthColumnDefinitionFormat;
public string StringColumnDefinition { get; }
public string StringLengthColumnDefinitionFormat { get; }
public string AutoIncrementDefinition = "AUTOINCREMENT";
public string IntColumnDefinition = "INTEGER";
public string LongColumnDefinition = "BIGINT";
public string GuidColumnDefinition = "GUID";
public string BoolColumnDefinition = "BOOL";
public string RealColumnDefinition = "DOUBLE";
public string DecimalColumnDefinition;
public string BlobColumnDefinition = "BLOB";
public string DateTimeColumnDefinition = "DATETIME";
public string TimeColumnDefinition = "DATETIME";
public string AutoIncrementDefinition { get; protected set; } = "AUTOINCREMENT";
public string IntColumnDefinition { get; protected set; } = "INTEGER";
public string LongColumnDefinition { get; protected set; } = "BIGINT";
public string GuidColumnDefinition { get; protected set; } = "GUID";
public string BoolColumnDefinition { get; protected set; } = "BOOL";
public string RealColumnDefinition { get; protected set; } = "DOUBLE";
public string DecimalColumnDefinition { get; protected set; }
public string BlobColumnDefinition { get; protected set; } = "BLOB";
public string DateTimeColumnDefinition { get; protected set; } = "DATETIME";
public string DateTimeOffsetColumnDefinition { get; protected set; } = "DATETIMEOFFSET(7)";
public string TimeColumnDefinition { get; protected set; } = "DATETIME";
protected IList<Func<ColumnDefinition, string>> ClauseOrder { get; }
protected DbTypes DbTypeMap = new DbTypes();
protected void InitColumnTypeMap()
protected DbTypes DbTypeMap => _dbTypes.Value;
private DbTypes InitColumnTypeMap()
{
DbTypeMap.Set<string>(DbType.String, StringColumnDefinition);
DbTypeMap.Set<char>(DbType.StringFixedLength, StringColumnDefinition);
DbTypeMap.Set<char?>(DbType.StringFixedLength, StringColumnDefinition);
DbTypeMap.Set<char[]>(DbType.String, StringColumnDefinition);
DbTypeMap.Set<bool>(DbType.Boolean, BoolColumnDefinition);
DbTypeMap.Set<bool?>(DbType.Boolean, BoolColumnDefinition);
DbTypeMap.Set<Guid>(DbType.Guid, GuidColumnDefinition);
DbTypeMap.Set<Guid?>(DbType.Guid, GuidColumnDefinition);
DbTypeMap.Set<DateTime>(DbType.DateTime, DateTimeColumnDefinition);
DbTypeMap.Set<DateTime?>(DbType.DateTime, DateTimeColumnDefinition);
DbTypeMap.Set<TimeSpan>(DbType.Time, TimeColumnDefinition);
DbTypeMap.Set<TimeSpan?>(DbType.Time, TimeColumnDefinition);
DbTypeMap.Set<DateTimeOffset>(DbType.Time, TimeColumnDefinition);
DbTypeMap.Set<DateTimeOffset?>(DbType.Time, TimeColumnDefinition);
var dbTypeMap = new DbTypesFactory();
dbTypeMap.Set<string>(DbType.String, StringColumnDefinition);
dbTypeMap.Set<char>(DbType.StringFixedLength, StringColumnDefinition);
dbTypeMap.Set<char?>(DbType.StringFixedLength, StringColumnDefinition);
dbTypeMap.Set<char[]>(DbType.String, StringColumnDefinition);
dbTypeMap.Set<bool>(DbType.Boolean, BoolColumnDefinition);
dbTypeMap.Set<bool?>(DbType.Boolean, BoolColumnDefinition);
dbTypeMap.Set<Guid>(DbType.Guid, GuidColumnDefinition);
dbTypeMap.Set<Guid?>(DbType.Guid, GuidColumnDefinition);
dbTypeMap.Set<DateTime>(DbType.DateTime, DateTimeColumnDefinition);
dbTypeMap.Set<DateTime?>(DbType.DateTime, DateTimeColumnDefinition);
dbTypeMap.Set<TimeSpan>(DbType.Time, TimeColumnDefinition);
dbTypeMap.Set<TimeSpan?>(DbType.Time, TimeColumnDefinition);
dbTypeMap.Set<DateTimeOffset>(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
dbTypeMap.Set<DateTimeOffset?>(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
DbTypeMap.Set<byte>(DbType.Byte, IntColumnDefinition);
DbTypeMap.Set<byte?>(DbType.Byte, IntColumnDefinition);
DbTypeMap.Set<sbyte>(DbType.SByte, IntColumnDefinition);
DbTypeMap.Set<sbyte?>(DbType.SByte, IntColumnDefinition);
DbTypeMap.Set<short>(DbType.Int16, IntColumnDefinition);
DbTypeMap.Set<short?>(DbType.Int16, IntColumnDefinition);
DbTypeMap.Set<ushort>(DbType.UInt16, IntColumnDefinition);
DbTypeMap.Set<ushort?>(DbType.UInt16, IntColumnDefinition);
DbTypeMap.Set<int>(DbType.Int32, IntColumnDefinition);
DbTypeMap.Set<int?>(DbType.Int32, IntColumnDefinition);
DbTypeMap.Set<uint>(DbType.UInt32, IntColumnDefinition);
DbTypeMap.Set<uint?>(DbType.UInt32, IntColumnDefinition);
dbTypeMap.Set<byte>(DbType.Byte, IntColumnDefinition);
dbTypeMap.Set<byte?>(DbType.Byte, IntColumnDefinition);
dbTypeMap.Set<sbyte>(DbType.SByte, IntColumnDefinition);
dbTypeMap.Set<sbyte?>(DbType.SByte, IntColumnDefinition);
dbTypeMap.Set<short>(DbType.Int16, IntColumnDefinition);
dbTypeMap.Set<short?>(DbType.Int16, IntColumnDefinition);
dbTypeMap.Set<ushort>(DbType.UInt16, IntColumnDefinition);
dbTypeMap.Set<ushort?>(DbType.UInt16, IntColumnDefinition);
dbTypeMap.Set<int>(DbType.Int32, IntColumnDefinition);
dbTypeMap.Set<int?>(DbType.Int32, IntColumnDefinition);
dbTypeMap.Set<uint>(DbType.UInt32, IntColumnDefinition);
dbTypeMap.Set<uint?>(DbType.UInt32, IntColumnDefinition);
DbTypeMap.Set<long>(DbType.Int64, LongColumnDefinition);
DbTypeMap.Set<long?>(DbType.Int64, LongColumnDefinition);
DbTypeMap.Set<ulong>(DbType.UInt64, LongColumnDefinition);
DbTypeMap.Set<ulong?>(DbType.UInt64, LongColumnDefinition);
dbTypeMap.Set<long>(DbType.Int64, LongColumnDefinition);
dbTypeMap.Set<long?>(DbType.Int64, LongColumnDefinition);
dbTypeMap.Set<ulong>(DbType.UInt64, LongColumnDefinition);
dbTypeMap.Set<ulong?>(DbType.UInt64, LongColumnDefinition);
DbTypeMap.Set<float>(DbType.Single, RealColumnDefinition);
DbTypeMap.Set<float?>(DbType.Single, RealColumnDefinition);
DbTypeMap.Set<double>(DbType.Double, RealColumnDefinition);
DbTypeMap.Set<double?>(DbType.Double, RealColumnDefinition);
dbTypeMap.Set<float>(DbType.Single, RealColumnDefinition);
dbTypeMap.Set<float?>(DbType.Single, RealColumnDefinition);
dbTypeMap.Set<double>(DbType.Double, RealColumnDefinition);
dbTypeMap.Set<double?>(DbType.Double, RealColumnDefinition);
DbTypeMap.Set<decimal>(DbType.Decimal, DecimalColumnDefinition);
DbTypeMap.Set<decimal?>(DbType.Decimal, DecimalColumnDefinition);
dbTypeMap.Set<decimal>(DbType.Decimal, DecimalColumnDefinition);
dbTypeMap.Set<decimal?>(DbType.Decimal, DecimalColumnDefinition);
DbTypeMap.Set<byte[]>(DbType.Binary, BlobColumnDefinition);
dbTypeMap.Set<byte[]>(DbType.Binary, BlobColumnDefinition);
return dbTypeMap.Create();
}
public abstract string ProviderName { get; }
@@ -193,17 +197,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
return indexType;
}
public virtual string GetSpecialDbType(SpecialDbTypes dbTypes)
public virtual string GetSpecialDbType(SpecialDbType dbType)
{
if (dbTypes == SpecialDbTypes.NCHAR)
if (dbType == SpecialDbType.NCHAR)
{
return "NCHAR";
return SpecialDbType.NCHAR;
}
else if (dbTypes == SpecialDbTypes.NTEXT)
else if (dbType == SpecialDbType.NTEXT)
{
return "NTEXT";
return SpecialDbType.NTEXT;
}
else if (dbTypes == SpecialDbTypes.NVARCHARMAX)
else if (dbType == SpecialDbType.NVARCHARMAX)
{
return "NVARCHAR(MAX)";
}
@@ -470,14 +474,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false)
return column.CustomType;
if (column.HasSpecialDbType)
if (column.CustomDbType.HasValue)
{
if (column.Size != default(int))
if (column.Size != default)
{
return $"{GetSpecialDbType(column.DbType)}({column.Size})";
return $"{GetSpecialDbType(column.CustomDbType.Value)}({column.Size})";
}
return GetSpecialDbType(column.DbType);
return GetSpecialDbType(column.CustomDbType.Value);
}
var type = column.Type.HasValue
@@ -486,19 +490,19 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
if (type == typeof(string))
{
var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength;
var valueOrDefault = column.Size != default ? column.Size : DefaultStringLength;
return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault);
}
if (type == typeof(decimal))
{
var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision;
var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale;
var precision = column.Size != default ? column.Size : DefaultDecimalPrecision;
var scale = column.Precision != default ? column.Precision : DefaultDecimalScale;
return string.Format(DecimalColumnDefinitionFormat, precision, scale);
}
var definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value;
var dbTypeDefinition = column.Size != default(int)
var definition = DbTypeMap.ColumnTypeMap[type];
var dbTypeDefinition = column.Size != default
? $"{definition}({column.Size})"
: definition;
//NOTE Precision is left out

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Core.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
@@ -29,6 +30,7 @@ namespace Umbraco.Cms.Core.Routing
INotificationHandler<ContentMovingNotification>,
INotificationHandler<ContentMovedNotification>
{
private readonly ILogger<RedirectTrackingHandler> _logger;
private readonly IOptionsMonitor<WebRoutingSettings> _webRoutingSettings;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IRedirectUrlService _redirectUrlService;
@@ -36,8 +38,14 @@ namespace Umbraco.Cms.Core.Routing
private const string NotificationStateKey = "Umbraco.Cms.Core.Routing.RedirectTrackingHandler";
public RedirectTrackingHandler(IOptionsMonitor<WebRoutingSettings> webRoutingSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor)
public RedirectTrackingHandler(
ILogger<RedirectTrackingHandler> logger,
IOptionsMonitor<WebRoutingSettings> webRoutingSettings,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IRedirectUrlService redirectUrlService,
IVariationContextAccessor variationContextAccessor)
{
_logger = logger;
_webRoutingSettings = webRoutingSettings;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_redirectUrlService = redirectUrlService;
@@ -87,8 +95,8 @@ namespace Umbraco.Cms.Core.Routing
private void StoreOldRoute(IContent entity, OldRoutesDictionary oldRoutes)
{
var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content;
var entityContent = contentCache.GetById(entity.Id);
var contentCache = _publishedSnapshotAccessor.PublishedSnapshot?.Content;
var entityContent = contentCache?.GetById(entity.Id);
if (entityContent == null)
return;
@@ -112,7 +120,13 @@ namespace Umbraco.Cms.Core.Routing
private void CreateRedirects(OldRoutesDictionary oldRoutes)
{
var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content;
var contentCache = _publishedSnapshotAccessor.PublishedSnapshot?.Content;
if (contentCache == null)
{
_logger.LogWarning("Could not track redirects because there is no current published snapshot available.");
return;
}
foreach (var oldRoute in oldRoutes)
{

View File

@@ -18,6 +18,7 @@ namespace Umbraco.Cms.Core.Security
private string[] _allowedSections;
private int[] _startMediaIds;
private int[] _startContentIds;
private DateTime? _inviteDateUtc;
private static readonly DelegateEqualityComparer<int[]> s_startIdsComparer = new DelegateEqualityComparer<int[]>(
(groups, enumerable) => groups.UnsortedSequenceEqual(enumerable),
@@ -75,6 +76,15 @@ namespace Umbraco.Cms.Core.Security
public int[] CalculatedMediaStartNodeIds { get; set; }
public int[] CalculatedContentStartNodeIds { get; set; }
/// <summary>
/// Gets or sets invite date
/// </summary>
public DateTime? InviteDateUtc
{
get => _inviteDateUtc;
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _inviteDateUtc, nameof(InviteDateUtc));
}
/// <summary>
/// Gets or sets content start nodes assigned to the User (not ones assigned to the user's groups)
/// </summary>

View File

@@ -438,6 +438,13 @@ namespace Umbraco.Cms.Core.Security
user.LastLoginDate = dt;
}
if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.InviteDateUtc))
|| (user.InvitedDate?.ToUniversalTime() != identityUser.InviteDateUtc))
{
anythingChanged = true;
user.InvitedDate = identityUser.InviteDateUtc?.ToLocalTime();
}
if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc))
|| (user.LastPasswordChangeDate != default && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|| (identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value))

View File

@@ -74,6 +74,7 @@ namespace Umbraco.Cms.Core.Security
target.UserName = source.Username;
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();
target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime();
target.InviteDateUtc = source.InvitedDate?.ToUniversalTime();
target.EmailConfirmed = source.EmailConfirmedDate.HasValue;
target.Name = source.Name;
target.AccessFailedCount = source.FailedPasswordAttempts;
@@ -87,7 +88,7 @@ namespace Umbraco.Cms.Core.Security
target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null;
}
// TODO: We need to validate this mapping is OK, we need to get Umbraco.Code working
// Umbraco.Code.MapAll -Id -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled -ConcurrencyStamp -NormalizedEmail -NormalizedUserName -Roles
private void Map(IMember source, MemberIdentityUser target)
{
target.Email = source.Email;

View File

@@ -0,0 +1,33 @@
using System.Net;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Core.Services.Implement
{
public class BasicAuthService : IBasicAuthService
{
private BasicAuthSettings _basicAuthSettings;
public BasicAuthService(IOptionsMonitor<BasicAuthSettings> optionsMonitor)
{
_basicAuthSettings = optionsMonitor.CurrentValue;
optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings);
}
public bool IsBasicAuthEnabled() => _basicAuthSettings.Enabled;
public bool IsIpAllowListed(IPAddress clientIpAddress)
{
foreach (var allowedIpString in _basicAuthSettings.AllowedIPs)
{
if (IPNetwork.TryParse(allowedIpString, out IPNetwork allowedIp) && allowedIp.Contains(clientIpAddress))
{
return true;
}
}
return false;
}
}
}

View File

@@ -19,15 +19,16 @@
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.34" />
<PackageReference Include="MailKit" Version="2.13.0" />
<PackageReference Include="MailKit" Version="2.14.0" />
<PackageReference Include="IPNetwork2" Version="2.5.329" />
<PackageReference Include="Markdown" Version="2.2.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="5.0.7" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="5.0.8" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" />
<PackageReference Include="MiniProfiler.Shared" Version="4.2.22" />
<PackageReference Include="ncrontab" Version="3.3.1" />
@@ -40,12 +41,12 @@
<PackageReference Include="Serilog.Filters.Expressions" Version="2.1.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="1.0.5" />
<PackageReference Include="Serilog.Settings.AppSettings" Version="2.2.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.1" /> <!-- Explicit updated this nested dependency due to this https://github.com/dotnet/announcements/issues/178-->

View File

@@ -21,6 +21,8 @@ namespace Umbraco.Cms.Infrastructure.WebAssets
public const string UmbracoInitCssBundleName = "umbraco-backoffice-init-css";
public const string UmbracoCoreJsBundleName = "umbraco-backoffice-js";
public const string UmbracoExtensionsJsBundleName = "umbraco-backoffice-extensions-js";
public const string UmbracoNonOptimizedPackageJsBundleName = "umbraco-backoffice-non-optimized-js";
public const string UmbracoNonOptimizedPackageCssBundleName = "umbraco-backoffice-non-optimized-css";
public const string UmbracoTinyMceJsBundleName = "umbraco-tinymce-js";
public const string UmbracoUpgradeCssBundleName = "umbraco-authorize-upgrade-css";
@@ -51,47 +53,134 @@ namespace Umbraco.Cms.Infrastructure.WebAssets
{
// Create bundles
_runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName, false,
FormatPaths("lib/bootstrap-social/bootstrap-social.css",
"assets/css/umbraco.min.css",
"lib/font-awesome/css/font-awesome.min.css"));
_runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName,
BundlingOptions.NotOptimizedAndComposite,
FormatPaths(
"assets/css/umbraco.min.css",
"lib/bootstrap-social/bootstrap-social.css",
"lib/font-awesome/css/font-awesome.min.css"));
_runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName, false,
FormatPaths("assets/css/umbraco.min.css",
"lib/bootstrap-social/bootstrap-social.css",
"lib/font-awesome/css/font-awesome.min.css"));
_runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName,
BundlingOptions.NotOptimizedAndComposite,
FormatPaths(
"assets/css/umbraco.min.css",
"lib/bootstrap-social/bootstrap-social.css",
"lib/font-awesome/css/font-awesome.min.css"));
_runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName, false,
_runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName,
BundlingOptions.NotOptimizedAndComposite,
FormatPaths("assets/css/canvasdesigner.min.css"));
_runtimeMinifier.CreateJsBundle(UmbracoPreviewJsBundleName, false,
_runtimeMinifier.CreateJsBundle(UmbracoPreviewJsBundleName,
BundlingOptions.NotOptimizedAndComposite,
FormatPaths(GetScriptsForPreview()));
_runtimeMinifier.CreateJsBundle(UmbracoTinyMceJsBundleName, false,
_runtimeMinifier.CreateJsBundle(UmbracoTinyMceJsBundleName,
BundlingOptions.NotOptimizedAndComposite,
FormatPaths(GetScriptsForTinyMce()));
_runtimeMinifier.CreateJsBundle(UmbracoCoreJsBundleName, false,
_runtimeMinifier.CreateJsBundle(UmbracoCoreJsBundleName,
BundlingOptions.NotOptimizedAndComposite,
FormatPaths(GetScriptsForBackOfficeCore()));
// get the property editor assets
var propertyEditorAssets = ScanPropertyEditors()
.GroupBy(x => x.AssetType)
.ToDictionary(x => x.Key, x => x.Select(c => c.FilePath));
// get the back office custom assets
var customAssets = _customBackOfficeAssetsCollection.GroupBy(x => x.DependencyType).ToDictionary(x => x.Key, x => x.Select(c => c.FilePath));
var jsAssets = (customAssets.TryGetValue(AssetType.Javascript, out var customScripts) ? customScripts : Enumerable.Empty<string>())
.Union(propertyEditorAssets.TryGetValue(AssetType.Javascript, out var scripts) ? scripts : Enumerable.Empty<string>());
// This bundle includes all scripts from property editor assets,
// custom back office assets, and any scripts found in package manifests
// that have the default bundle options.
IEnumerable<string> jsAssets = (customAssets.TryGetValue(AssetType.Javascript, out IEnumerable<string> customScripts) ? customScripts : Enumerable.Empty<string>())
.Union(propertyEditorAssets.TryGetValue(AssetType.Javascript, out IEnumerable<string> scripts) ? scripts : Enumerable.Empty<string>());
_runtimeMinifier.CreateJsBundle(
UmbracoExtensionsJsBundleName, true,
UmbracoExtensionsJsBundleName,
BundlingOptions.OptimizedAndComposite,
FormatPaths(
GetScriptsForBackOfficeExtensions(jsAssets)));
var cssAssets = (customAssets.TryGetValue(AssetType.Css, out var customStyles) ? customStyles : Enumerable.Empty<string>())
.Union(propertyEditorAssets.TryGetValue(AssetType.Css, out var styles) ? styles : Enumerable.Empty<string>());
// Create a bundle per package manifest that is declaring an Independent bundle type
RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest.Scripts, AssetType.Javascript);
// Create a single non-optimized (no file processing) bundle for all manifests declaring None as a bundle option
RegisterPackageBundlesForNoneOption(_parser.CombinedManifest.Scripts, UmbracoNonOptimizedPackageJsBundleName);
// This bundle includes all CSS from property editor assets,
// custom back office assets, and any CSS found in package manifests
// that have the default bundle options.
IEnumerable<string> cssAssets = (customAssets.TryGetValue(AssetType.Css, out IEnumerable<string> customStyles) ? customStyles : Enumerable.Empty<string>())
.Union(propertyEditorAssets.TryGetValue(AssetType.Css, out IEnumerable<string> styles) ? styles : Enumerable.Empty<string>());
_runtimeMinifier.CreateCssBundle(
UmbracoCssBundleName, true,
UmbracoCssBundleName,
BundlingOptions.OptimizedAndComposite,
FormatPaths(
GetStylesheetsForBackOffice(cssAssets)));
// Create a bundle per package manifest that is declaring an Independent bundle type
RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest.Stylesheets, AssetType.Css);
// Create a single non-optimized (no file processing) bundle for all manifests declaring None as a bundle option
RegisterPackageBundlesForNoneOption(_parser.CombinedManifest.Stylesheets, UmbracoNonOptimizedPackageCssBundleName);
}
public static string GetIndependentPackageBundleName(ManifestAssets manifestAssets, AssetType assetType)
=> $"{manifestAssets.PackageName.ToLowerInvariant()}-{(assetType == AssetType.Css ? "css" : "js")}";
private void RegisterPackageBundlesForNoneOption(
IReadOnlyDictionary<BundleOptions, IReadOnlyList<ManifestAssets>> combinedPackageManifestAssets,
string bundleName)
{
var assets = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
// Create a bundle per package manifest that is declaring the matching BundleOptions
if (combinedPackageManifestAssets.TryGetValue(BundleOptions.None, out IReadOnlyList<ManifestAssets> manifestAssetList))
{
foreach(var asset in manifestAssetList.SelectMany(x => x.Assets))
{
assets.Add(asset);
}
}
_runtimeMinifier.CreateJsBundle(
bundleName,
// no optimization, no composite files, just render individual files
BundlingOptions.NotOptimizedNotComposite,
FormatPaths(assets.ToArray()));
}
private void RegisterPackageBundlesForIndependentOptions(
IReadOnlyDictionary<BundleOptions, IReadOnlyList<ManifestAssets>> combinedPackageManifestAssets,
AssetType assetType)
{
// Create a bundle per package manifest that is declaring the matching BundleOptions
if (combinedPackageManifestAssets.TryGetValue(BundleOptions.Independent, out IReadOnlyList<ManifestAssets> manifestAssetList))
{
foreach (ManifestAssets manifestAssets in manifestAssetList)
{
string bundleName = GetIndependentPackageBundleName(manifestAssets, assetType);
string[] filePaths = FormatPaths(manifestAssets.Assets.ToArray());
switch (assetType)
{
case AssetType.Javascript:
_runtimeMinifier.CreateJsBundle(bundleName, BundlingOptions.OptimizedAndComposite, filePaths);
break;
case AssetType.Css:
_runtimeMinifier.CreateCssBundle(bundleName, BundlingOptions.OptimizedAndComposite, filePaths);
break;
default:
throw new IndexOutOfRangeException();
}
}
}
}
/// <summary>
@@ -100,10 +189,15 @@ namespace Umbraco.Cms.Infrastructure.WebAssets
/// <returns></returns>
private string[] GetScriptsForBackOfficeExtensions(IEnumerable<string> propertyEditorScripts)
{
var scripts = new HashSet<string>();
foreach (string script in _parser.Manifest.Scripts)
var scripts = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
// only include scripts with the default bundle options here
if (_parser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Default, out IReadOnlyList<ManifestAssets> manifestAssets))
{
scripts.Add(script);
foreach (string script in manifestAssets.SelectMany(x => x.Assets))
{
scripts.Add(script);
}
}
foreach (string script in propertyEditorScripts)
@@ -130,11 +224,15 @@ namespace Umbraco.Cms.Infrastructure.WebAssets
/// <returns></returns>
private string[] GetStylesheetsForBackOffice(IEnumerable<string> propertyEditorStyles)
{
var stylesheets = new HashSet<string>();
var stylesheets = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
foreach (string script in _parser.Manifest.Stylesheets)
// only include css with the default bundle options here
if (_parser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Default, out IReadOnlyList<ManifestAssets> manifestAssets))
{
stylesheets.Add(script);
foreach (string script in manifestAssets.SelectMany(x => x.Assets))
{
stylesheets.Add(script);
}
}
foreach (string stylesheet in propertyEditorStyles)

View File

@@ -1,41 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.WebAssets;
using Umbraco.Cms.Infrastructure.WebAssets;
namespace Umbraco.Extensions
{
public static class RuntimeMinifierExtensions
{
/// <summary>
/// Returns the JavaScript to load the back office's assets
/// </summary>
/// <returns></returns>
public static async Task<string> GetScriptForLoadingBackOfficeAsync(this IRuntimeMinifier minifier, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
{
var coreScripts = await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName);
var extensionsScripts = await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoExtensionsJsBundleName);
var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization(coreScripts.Union(extensionsScripts), "umbraco", globalSettings, hostingEnvironment);
result += await GetStylesheetInitializationAsync(minifier);
return result;
}
/// <summary>
/// Gets the back office css bundle paths and formats a JS call to lazy load them
/// </summary>
private static async Task<string> GetStylesheetInitializationAsync(IRuntimeMinifier minifier)
{
var files = await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName);
var sb = new StringBuilder();
foreach (var file in files)
sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file);
return sb.ToString();
}
}
}

View File

@@ -17,13 +17,12 @@ namespace Umbraco.Cms.Persistence.SqlCe
public int BulkInsertRecords<T>(IUmbracoDatabase database, IEnumerable<T> records)
{
var recordsA = records.ToArray();
if (recordsA.Length == 0) return 0;
if (!records.Any()) return 0;
var pocoData = database.PocoDataFactory.ForType(typeof(T));
if (pocoData == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
return BulkInsertRecordsSqlCe(database, pocoData, recordsA);
return BulkInsertRecordsSqlCe(database, pocoData, records.ToArray());
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
@@ -25,10 +25,10 @@ namespace Umbraco.Cms.Persistence.SqlCe
{
_globalSettings = globalSettings;
BlobColumnDefinition = "IMAGE";
// This is silly to have to do this but the way these inherited classes are structured it's the easiest
// way without an overhaul in type map initialization
DbTypeMap.Set<byte[]>(DbType.Binary, BlobColumnDefinition);
// NOTE: if this column type is used in sqlce, it will prob result in errors since
// SQLCE cannot support this type correctly without 2x columns and a lot of work arounds.
// We don't use this natively within Umbraco but 3rd parties might with SQL server.
DateTimeOffsetColumnDefinition = "DATETIME";
}
public override string ProviderName => Constants.DatabaseProviders.SqlCe;
@@ -300,10 +300,14 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
GetQuotedTableName(index.TableName), columns);
}
public override string GetSpecialDbType(SpecialDbTypes dbTypes)
public override string GetSpecialDbType(SpecialDbType dbTypes)
{
if (dbTypes == SpecialDbTypes.NVARCHARMAX) // SqlCE does not have nvarchar(max) for now
// SqlCE does not have nvarchar(max) for now
if (dbTypes == SpecialDbType.NVARCHARMAX)
{
return "NTEXT";
}
return base.GetSpecialDbType(dbTypes);
}
public override SqlDbType GetSqlDbType(DbType dbType)

View File

@@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using CSharpTest.Net.Serialization;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
@@ -6,7 +6,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Serializes/Deserializes data to BTree data source for <see cref="ContentData"/>
/// </summary>
internal class ContentDataSerializer : ISerializer<ContentData>
public class ContentDataSerializer : ISerializer<ContentData>
{
public ContentDataSerializer(IDictionaryOfPropertyDataSerializer dictionaryOfPropertyDataSerializer = null)
{

View File

@@ -1,11 +1,11 @@
using System.Configuration;
using System.Configuration;
using CSharpTest.Net.Collections;
using CSharpTest.Net.Serialization;
using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
{
internal class BTree
public class BTree
{
public static BPlusTree<int, ContentNodeKit> GetTree(string filepath, bool exists, NuCacheSettings settings, ContentDataSerializer contentDataSerializer = null)
{

View File

@@ -14,12 +14,12 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Deserialize the data into a <see cref="ContentCacheDataModel"/>
/// </summary>
ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData);
ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData, bool published);
/// <summary>
/// Serializes the <see cref="ContentCacheDataModel"/>
/// Serializes the <see cref="ContentCacheDataModel"/>
/// </summary>
ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model);
ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published);
}
}

View File

@@ -3,7 +3,7 @@ using System.IO;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
{
internal interface IDictionaryOfPropertyDataSerializer
public interface IDictionaryOfPropertyDataSerializer
{
IDictionary<string, PropertyData[]> ReadFrom(Stream stream);
void WriteTo(IDictionary<string, PropertyData[]> value, Stream stream);

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
DateFormatString = "o"
};
private readonly JsonNameTable _propertyNameTable = new DefaultJsonNameTable();
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData)
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData, bool published)
{
if (stringData == null && byteData != null)
throw new NotSupportedException($"{typeof(JsonContentNestedDataSerializer)} does not support byte[] serialization");
@@ -39,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
}
}
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model)
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published)
{
// note that numeric values (which are Int32) are serialized without their
// type (eg "value":1234) and JsonConvert by default deserializes them as Int64

View File

@@ -39,7 +39,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
_options = defaultOptions
.WithResolver(resolver)
.WithCompression(MessagePackCompression.Lz4BlockArray);
.WithCompression(MessagePackCompression.Lz4BlockArray)
.WithSecurity(MessagePackSecurity.UntrustedData);
}
public string ToJson(byte[] bin)
@@ -48,12 +49,12 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
return json;
}
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData)
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData, bool published)
{
if (byteData != null)
{
var cacheModel = MessagePackSerializer.Deserialize<ContentCacheDataModel>(byteData, _options);
Expand(content, cacheModel);
Expand(content, cacheModel, published);
return cacheModel;
}
else if (stringData != null)
@@ -61,7 +62,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
// NOTE: We don't really support strings but it's possible if manually used (i.e. tests)
var bin = Convert.FromBase64String(stringData);
var cacheModel = MessagePackSerializer.Deserialize<ContentCacheDataModel>(bin, _options);
Expand(content, cacheModel);
Expand(content, cacheModel, published);
return cacheModel;
}
else
@@ -70,9 +71,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
}
}
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model)
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published)
{
Compress(content, model);
Compress(content, model, published);
var bytes = MessagePackSerializer.Serialize(model, _options);
return new ContentCacheDataSerializationResult(null, bytes);
}
@@ -80,7 +81,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Used during serialization to compress properties
/// </summary>
/// <param name="content"></param>
/// <param name="model"></param>
/// <param name="published"></param>
/// <remarks>
/// This will essentially 'double compress' property data. The MsgPack data as a whole will already be compressed
/// but this will go a step further and double compress property data so that it is stored in the nucache file
@@ -88,11 +91,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// read/decompressed as a string to be displayed on the front-end. This allows for potentially a significant
/// memory savings but could also affect performance of first rendering pages while decompression occurs.
/// </remarks>
private void Compress(IReadOnlyContentBase content, ContentCacheDataModel model)
private void Compress(IReadOnlyContentBase content, ContentCacheDataModel model, bool published)
{
foreach(var propertyAliasToData in model.PropertyData)
{
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key))
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key, published))
{
foreach(var property in propertyAliasToData.Value.Where(x => x.Value != null && x.Value is string))
{
@@ -105,12 +108,14 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Used during deserialization to map the property data as lazy or expand the value
/// </summary>
/// <param name="content"></param>
/// <param name="nestedData"></param>
private void Expand(IReadOnlyContentBase content, ContentCacheDataModel nestedData)
/// <param name="published"></param>
private void Expand(IReadOnlyContentBase content, ContentCacheDataModel nestedData, bool published)
{
foreach (var propertyAliasToData in nestedData.PropertyData)
{
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key))
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key,published))
{
foreach (var property in propertyAliasToData.Value.Where(x => x.Value != null))
{

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
{
@@ -14,7 +13,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
private readonly IMemberTypeService _memberTypeService;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IPropertyCacheCompressionOptions _compressionOptions;
private readonly ConcurrentDictionary<(int, string), bool> _isCompressedCache = new ConcurrentDictionary<(int, string), bool>();
private readonly ConcurrentDictionary<(int, string, bool), bool> _isCompressedCache = new ConcurrentDictionary<(int, string, bool), bool>();
public MsgPackContentNestedDataSerializerFactory(
IContentTypeService contentTypeService,

View File

@@ -68,7 +68,20 @@ namespace Umbraco.Extensions
throw new IndexOutOfRangeException();
}
});
builder.Services.AddSingleton<IPropertyCacheCompressionOptions, NoopPropertyCacheCompressionOptions>();
builder.Services.AddSingleton<IPropertyCacheCompressionOptions>(s =>
{
IOptions<NuCacheSettings> options = s.GetRequiredService<IOptions<NuCacheSettings>>();
if (options.Value.NuCacheSerializerType == NuCacheSerializerType.MessagePack &&
options.Value.UnPublishedContentCompression)
{
return new UnPublishedContentPropertyCacheCompressionOptions();
}
return new NoopPropertyCacheCompressionOptions();
});
builder.Services.AddSingleton(s => new ContentDataSerializer(new DictionaryOfPropertyDataSerializer()));
// add the NuCache health check (hidden from type finder)

View File

@@ -415,7 +415,7 @@ AND cmsContentNu.nodeId IS NULL
UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders)
};
var serialized = serializer.Serialize(ReadOnlyContentBaseAdapter.Create(content), contentCacheData);
var serialized = serializer.Serialize(ReadOnlyContentBaseAdapter.Create(content), contentCacheData, published);
var dto = new ContentNuDto
{
@@ -817,12 +817,13 @@ AND cmsContentNu.nodeId IS NULL
}
else
{
var deserializedContent = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw);
bool published = false;
var deserializedContent = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
d = new ContentData
{
Name = dto.EditName,
Published = false,
Published = published,
TemplateId = dto.EditTemplateId,
VersionId = dto.VersionId,
VersionDate = dto.EditVersionDate,
@@ -847,13 +848,14 @@ AND cmsContentNu.nodeId IS NULL
}
else
{
var deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw);
bool published = true;
var deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw, published);
p = new ContentData
{
Name = dto.PubName,
UrlSegment = deserializedContent.UrlSegment,
Published = true,
Published = published,
TemplateId = dto.PubTemplateId,
VersionId = dto.VersionId,
VersionDate = dto.PubVersionDate,
@@ -883,12 +885,13 @@ AND cmsContentNu.nodeId IS NULL
if (dto.EditData == null && dto.EditDataRaw == null)
throw new InvalidOperationException("No data for media " + dto.Id);
var deserializedMedia = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw);
bool published = true;
var deserializedMedia = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
var p = new ContentData
{
Name = dto.EditName,
Published = true,
Published = published,
TemplateId = -1,
VersionId = dto.VersionId,
VersionDate = dto.EditVersionDate,

View File

@@ -19,8 +19,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MessagePack" Version="2.2.85" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.6" />
<PackageReference Include="MessagePack" Version="2.3.75" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.12" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Umbraco.Code" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>

View File

@@ -0,0 +1,16 @@
namespace Umbraco.TestData.Configuration
{
public class TestDataSettings
{
/// <summary>
/// Gets or sets a value indicating whether the test data generation is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether persisted local database cache files for content and media are disabled.
/// </summary>
/// <value>The URL path.</value>
public bool IgnoreLocalDb { get; set; } = false;
}
}

Some files were not shown because too many files have changed in this diff Show More