Fix issues with added PanicException
This commit is contained in:
+2
-2
@@ -1,4 +1,4 @@
|
||||
# Umbraco Cms Build
|
||||
# Umbraco CMS Build
|
||||
|
||||
## Are you sure?
|
||||
|
||||
@@ -66,7 +66,7 @@ The Visual Studio object is `null` when Visual Studio has not been detected (eg
|
||||
* `Path`: Visual Studio installation path (eg some place under `Program Files`)
|
||||
* `Major`: Visual Studio major version (eg `15` for VS 2017)
|
||||
* `Minor`: Visual Studio minor version
|
||||
* `MsBUild`: the absolute path to the MsBuild executable
|
||||
* `MsBuild`: the absolute path to the MsBuild executable
|
||||
|
||||
#### GetUmbracoVersion
|
||||
|
||||
|
||||
@@ -29,4 +29,4 @@ Don't rest on your laurels and never accept the status quo. Contribute and give
|
||||
|
||||
## Friendly
|
||||
|
||||
Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and builds long lasting relationships.
|
||||
Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and build long lasting relationships.
|
||||
+37
-29
@@ -2,15 +2,15 @@
|
||||
|
||||
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
|
||||
|
||||
The following is a set of guidelines for contributing to Umbraco CMS.
|
||||
The following is a set of guidelines, for contributing to Umbraco CMS.
|
||||
|
||||
These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
||||
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).
|
||||
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**
|
||||
|
||||
@@ -38,11 +38,11 @@ This document gives you a quick overview on how to get started.
|
||||
|
||||
### Guidelines for contributions we welcome
|
||||
|
||||
Not all changes are wanted, so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
|
||||
Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
|
||||
|
||||
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes.
|
||||
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, if an issue is in the `Up for grabs` list or you've asked for some feedback before you sent us a PR, your PR will not be closed as unwanted.
|
||||
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.
|
||||
|
||||
### What can I start with?
|
||||
|
||||
@@ -64,32 +64,32 @@ Great question! The short version goes like this:
|
||||
* **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/dev`, 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). 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 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.
|
||||
|
||||

|
||||
|
||||
### Pull requests
|
||||
The most successful pull requests usually look a like this:
|
||||
|
||||
* Fill in the required template
|
||||
* Fill in the required template, linking 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
|
||||
* 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.
|
||||
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?
|
||||
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 you to make some additional changes.
|
||||
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
|
||||
- Sometimes it is difficult to meet these timelines and we'll talk to you if this is the case.
|
||||
|
||||
### Styleguides
|
||||
|
||||
@@ -99,21 +99,21 @@ That said, the Umbraco development team likes to follow the hints that ReSharper
|
||||
|
||||
### The PR team
|
||||
|
||||
The pull request team consists of a member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members
|
||||
The pull request 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:
|
||||
|
||||
- [Anders Bjerner](https://github.com/abjerner)
|
||||
- [Dave Woestenborghs](https://github.com/dawoe)
|
||||
- [Emma Burstow](https://github.com/emmaburstow)
|
||||
- [Poornima Nayar](https://github.com/poornimanayar)
|
||||
|
||||
These wonderful volunteers will provide you with a first reply to your PR, review and test out your changes and might ask more questions. After that they'll let Umbraco HQ know if everything seems okay.
|
||||
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 PR team](#the-pr-team) in multiple ways, we love open conversations and we are a friendly bunch. No question you have is stupid. Any questions you have usually helps out multiple people with the same question. Ask away:
|
||||
You can get in touch with [the PR team](#the-pr-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
|
||||
- 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
|
||||
|
||||
@@ -125,19 +125,19 @@ In order to build the Umbraco source code locally, first make sure you have the
|
||||
* Node v10+
|
||||
* npm v6.4.1+
|
||||
|
||||
The easiest way to get started is to run `build.ps1` 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.
|
||||
The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2017 (version 15.9.7 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 open `src\umbraco.sln` in Visual Studio 2017 (version 15.9.7 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
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's two big areas that you should know about:
|
||||
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:
|
||||
@@ -146,30 +146,34 @@ There's two big areas that you should know about:
|
||||
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 of something you're looking to fix or improve, have a look at the following two parts of the API documentation.
|
||||
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/), 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/dev`. Whatever the default is, that's where we'd like you to target your 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/dev`. 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.
|
||||
|
||||

|
||||
|
||||
### Making changes after the PR was opened
|
||||
### 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 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.
|
||||
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've 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.
|
||||
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:
|
||||
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
|
||||
@@ -185,3 +189,7 @@ git rebase upstream/v8/dev
|
||||
In this command we're syncing with the `v8/dev` 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 PR team 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.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Contributing to Umbraco CMS
|
||||
|
||||
When you’re considering creating a pull request for Umbraco CMS, we will categorize them in two different sizes, small and large.
|
||||
|
||||
The process for both sizes is very similar, as [explained in the contribution document](CONTRIBUTING.md#how-do-i-begin).
|
||||
|
||||
## Small PRs
|
||||
Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files.
|
||||
|
||||
We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process](REVIEW_PROCESS.md).
|
||||
|
||||
### Up for grabs
|
||||
|
||||
Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
|
||||
|
||||
If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
|
||||
|
||||
## Large PRs
|
||||
New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.).
|
||||
|
||||
We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
|
||||
|
||||
Please make sure to describe your idea in an issue, it helps to put in mockup screenshots or videos.
|
||||
|
||||
If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
|
||||
|
||||
If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the [small PRs process](#small-prs) above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
|
||||
|
||||
It is highly recommended that you speak to the HQ before making large, complex changes.
|
||||
|
||||
### Pull request or package?
|
||||
|
||||
If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs.
|
||||
|
||||
Eventually, a package could "graduate" to be included in the CMS.
|
||||
@@ -7,15 +7,15 @@ A brief description of the issue goes here.
|
||||
|
||||
<!--
|
||||
|
||||
If you haven't yet done so, please read the "contributing guidelines"
|
||||
thoroughly. Then, proceed by filling out the rest of the details in the issue
|
||||
template below. The more details you can give us, the easier it will be for us
|
||||
Please fill out the rest of the details in the issue template below.
|
||||
The more details you can give us, the easier it will be for us
|
||||
to determine the cause of a problem.
|
||||
|
||||
See: https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/.github/CONTRIBUTING.md
|
||||
|
||||
-->
|
||||
|
||||
## Umbraco version
|
||||
|
||||
I am seeing this issue on Umbraco version: <!-- please note the version here -->
|
||||
|
||||
|
||||
Reproduction
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://pullreminders.com?ref=badge)
|
||||
# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
|
||||
|
||||
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
@@ -21,7 +21,7 @@ Please also see our [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
[Umbraco Cloud](https://umbraco.com/cloud) is the easiest and fastest way to use Umbraco yet, with full support for all your custom .NET code and integrations. You're up and running in less than a minute, and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14-day trial, no credit card needed.
|
||||
|
||||
If you want to DIY, you can [download Umbraco]((https://our.umbraco.com/download)) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host it yourself, and handling deployments and upgrades will be all up to you.
|
||||
If you want to DIY, then you can [download Umbraco]((https://our.umbraco.com/download)) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host it yourself, and handling deployments and upgrades will be all up to you.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -29,7 +29,7 @@ The documentation for Umbraco CMS can be found [on Our Umbraco](https://our.umbr
|
||||
|
||||
## Join the Umbraco community
|
||||
|
||||
Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.com/). Our Umbraco features forums for questions and answers, documentation, downloadable plugins for Umbraco, and a rich collection of community resources.
|
||||
Our friendly community is available 24/7 at the community hub, we call ["Our Umbraco"](https://our.umbraco.com/). Our Umbraco features forums for questions and answers, documentation, downloadable plugins for Umbraco, and a rich collection of community resources.
|
||||
|
||||
Besides "Our", we all support each other also via Twitter: [Umbraco HQ](https://twitter.com/umbraco), [Release Updates](https://twitter.com/umbracoproject), [#umbraco](https://twitter.com/hashtag/umbraco)
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Review process
|
||||
|
||||
You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response.
|
||||
|
||||
In order to set some expectations, here's what happens next.
|
||||
|
||||
## Review process
|
||||
|
||||
You will get an initial reply within 48 hours (workdays) to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can.
|
||||
|
||||
You will get feedback within at most 14 days after opening the PR. You’ll most likely get feedback sooner though. Then there are a few possible outcomes:
|
||||
|
||||
- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
|
||||
- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
|
||||
- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
|
||||
- Your proposed change is awesome but.. not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!)
|
||||
|
||||
## Are you still available?
|
||||
|
||||
We understand you have other things to do and can't just drop everything to help us out.
|
||||
So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
|
||||
|
||||
If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
|
||||
|
||||
There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
|
||||
+2
-1
@@ -132,7 +132,8 @@ src/Umbraco.Web.UI.Client/bower_components/*
|
||||
preserve.belle
|
||||
|
||||
#Ignore Rule for output of generated documentation files from Grunt docserve
|
||||
src/Umbraco.Web.UI.Client/docs/api
|
||||
src/Umbraco.Web.UI.Docs/api
|
||||
src/Umbraco.Web.UI.Docs/package-lock.json
|
||||
src/*.boltdata/
|
||||
src/umbraco.sln.ide/*
|
||||
src/.vs/
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
not want this to happen as the alpha of the next major is, really, the next major already.
|
||||
-->
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="ClientDependency" version="[1.9.7,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0,1.999999)" />
|
||||
<dependency id="ClientDependency" version="[1.9.8,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.9.3,1.999999)" />
|
||||
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
|
||||
<dependency id="Examine" version="[1.0.0,1.999999)" />
|
||||
<dependency id="Examine" version="[1.0.1,1.999999)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.8.14,1.999999)" />
|
||||
<dependency id="ImageProcessor" version="[2.7.0.100,2.999999)" />
|
||||
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
<dependency id="ImageProcessor.Web" version="[4.10.0.100,4.999999)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.5.0.100,2.999999)" />
|
||||
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
|
||||
<dependency id="Microsoft.Net.Compilers" version="[2.10.0,2.999999)" />
|
||||
|
||||
</group>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
888
|
||||
888
|
||||
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
|
||||
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
|
||||
888 888 888 888 888 888 888 888 .d888888 888 888 888
|
||||
Y88b 888 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y88888 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
|
||||
888
|
||||
888
|
||||
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
|
||||
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
|
||||
888 888 888 888 888 888 888 888 .d888888 888 888 888
|
||||
Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
|
||||
_ _ __ __ ____ _____ _____ ____
|
||||
| | | | \/ | _ \| __ \ /\ / ____/ __ \
|
||||
| | | | \ / | |_) | |__) | / \ | | | | | |
|
||||
| | | | |\/| | _ <| _ / / /\ \| | | | | |
|
||||
| |__| | | | | |_) | | \ \ / ____ | |___| |__| |
|
||||
\____/|_| |_|____/|_| \_/_/ \_\_____\____/
|
||||
888
|
||||
888
|
||||
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
|
||||
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
|
||||
888 888 888 888 888 888 888 888 .d888888 888 888 888
|
||||
Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
|
||||
|
||||
----------------------------------------------------
|
||||
------------------------------------------------------------------
|
||||
|
||||
Don't forget to build!
|
||||
|
||||
@@ -25,6 +26,6 @@ The following items will now be automatically included when creating a deploy pa
|
||||
system: umbraco, config\splashes and global.asax.
|
||||
|
||||
Please read the release notes on our.umbraco.com:
|
||||
http://our.umbraco.com/contribute/releases
|
||||
https://our.umbraco.com/contribute/releases
|
||||
|
||||
- Umbraco
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<add name="umbracoBaseRequestModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
|
||||
<add name="viewstateMoverModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
|
||||
<add name=" UmbracoModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
|
||||
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,umbraco" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
|
||||
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,Umbraco.Web" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
|
||||
</httpModules>
|
||||
|
||||
<httpHandlers xdt:Transform="InsertIfMissing" />
|
||||
@@ -76,7 +76,7 @@
|
||||
<!-- Note, we're removing the one that starts with a space here, don't correct it -->
|
||||
<!-- This to fix a quirk we for a lot of releases where we added it with the space by default -->
|
||||
<add name=" UmbracoModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
|
||||
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,umbraco" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
|
||||
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,Umbraco.Web" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
|
||||
</modules>
|
||||
|
||||
<staticContent xdt:Transform="InsertIfMissing" />
|
||||
@@ -128,6 +128,7 @@
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Data.SqlServerCe')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.ValueTuple')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Net.Http.Formatting')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Collections.Immutable')" xdt:Transform="Remove" />
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
$uenv=build/build.ps1 -get
|
||||
|
||||
$src = "$($uenv.SolutionRoot)\src"
|
||||
$tmp = $uenv.BuildTemp
|
||||
$out = $uenv.BuildOutput
|
||||
$DocFxJson = "$src\ApiDocs\docfx.json"
|
||||
$DocFxSiteOutput = "$tmp\_site\*.*"
|
||||
|
||||
################ Do the UI docs
|
||||
$uenv.CompileBelle()
|
||||
|
||||
"Moving to Umbraco.Web.UI.Client folder"
|
||||
cd .\src\Umbraco.Web.UI.Client
|
||||
|
||||
"Generating the docs and waiting before executing the next commands"
|
||||
& gulp docs | Out-Null
|
||||
|
||||
# change baseUrl
|
||||
$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
|
||||
$IndexPath = "./docs/api/index.html"
|
||||
(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
|
||||
|
||||
# zip it
|
||||
& $uenv.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*"
|
||||
|
||||
|
||||
################ Do the c# docs
|
||||
|
||||
# Build the solution in debug mode
|
||||
$SolutionPath = Join-Path -Path $src -ChildPath "umbraco.sln"
|
||||
#$uenv.CompileUmbraco()
|
||||
|
||||
#restore nuget packages
|
||||
$uenv.RestoreNuGet()
|
||||
|
||||
# run DocFx
|
||||
$DocFx = $uenv.BuildEnv.DocFx
|
||||
|
||||
Write-Host "$DocFxJson"
|
||||
& $DocFx metadata $DocFxJson
|
||||
& $DocFx build $DocFxJson
|
||||
|
||||
# zip it
|
||||
& $uenv.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput
|
||||
+9
-10
@@ -456,25 +456,24 @@
|
||||
$ubuild.DefineMethod("PrepareAngularDocs",
|
||||
{
|
||||
Write-Host "Prepare Angular Documentation"
|
||||
|
||||
|
||||
$src = "$($this.SolutionRoot)\src"
|
||||
$out = $this.BuildOutput
|
||||
|
||||
$this.CompileBelle()
|
||||
|
||||
"Moving to Umbraco.Web.UI.Client folder"
|
||||
cd .\src\Umbraco.Web.UI.Client
|
||||
$out = $this.BuildOutput
|
||||
|
||||
"Moving to Umbraco.Web.UI.Docs folder"
|
||||
cd ..\src\Umbraco.Web.UI.Docs
|
||||
|
||||
"Generating the docs and waiting before executing the next commands"
|
||||
& gulp docs | Out-Null
|
||||
& npm install
|
||||
& npx gulp docs
|
||||
|
||||
# change baseUrl
|
||||
$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
|
||||
$IndexPath = "./docs/api/index.html"
|
||||
$IndexPath = "./api/index.html"
|
||||
(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
|
||||
|
||||
# zip it
|
||||
& $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*"
|
||||
& $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Docs\api\*.*"
|
||||
})
|
||||
|
||||
$ubuild.DefineMethod("Build",
|
||||
|
||||
@@ -1,65 +1,11 @@
|
||||
body {
|
||||
color: rgba(0,0,0,.8);
|
||||
}
|
||||
.navbar-inverse {
|
||||
background: #a3db78;
|
||||
}
|
||||
.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text {
|
||||
color: rgba(0,0,0,.8);
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.sidetoc {
|
||||
background-color: #f5fbf1;
|
||||
}
|
||||
body .toc {
|
||||
background-color: #f5fbf1;
|
||||
}
|
||||
.sidefilter {
|
||||
background-color: #daf0c9;
|
||||
}
|
||||
.subnav {
|
||||
background-color: #f5fbf1;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a {
|
||||
color: rgba(0,0,0,.8);
|
||||
background-color: #daf0c9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover {
|
||||
color: rgba(0,0,0,.8);
|
||||
background-color: #daf0c9;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: rgba(0,0,0,.8);
|
||||
background-color: #fff;
|
||||
border-color: rgba(0,0,0,.8);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #daf0c9;
|
||||
color: rgba(0,0,0,.8);
|
||||
border-color: rgba(0,0,0,.8);
|
||||
}
|
||||
|
||||
.toc .nav > li > a {
|
||||
color: rgba(0,0,0,.8);
|
||||
}
|
||||
|
||||
button, a {
|
||||
color: #f36f21;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus,
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: #143653;
|
||||
text-decoration: none;
|
||||
color: #34393e;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
word-wrap: break-word
|
||||
}
|
||||
|
||||
.navbar-header .navbar-brand {
|
||||
@@ -68,6 +14,202 @@ a:focus {
|
||||
width:50px;
|
||||
}
|
||||
|
||||
.toc .nav > li.active > a {
|
||||
color: #f36f21;
|
||||
#_content>a {
|
||||
margin-top: 5px;
|
||||
}
|
||||
/* HEADINGS */
|
||||
|
||||
h1 {
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
article h1,
|
||||
article h2,
|
||||
article h3,
|
||||
article h4 {
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
article h4 {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
/* NAVBAR */
|
||||
|
||||
.navbar-brand>img {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border: none;
|
||||
/* Both navbars use box-shadow */
|
||||
-webkit-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5);
|
||||
-moz-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5);
|
||||
box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5);
|
||||
}
|
||||
|
||||
.subnav {
|
||||
border-top: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
background-color: #303ea1;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a,
|
||||
.navbar-inverse .navbar-text {
|
||||
color: #fff;
|
||||
background-color: #303ea1;
|
||||
border-bottom: 3px solid transparent;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a:focus,
|
||||
.navbar-inverse .navbar-nav>li>a:hover {
|
||||
color: #fff;
|
||||
background-color: #303ea1;
|
||||
border-bottom: 3px solid white;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a,
|
||||
.navbar-inverse .navbar-nav>.active>a:focus,
|
||||
.navbar-inverse .navbar-nav>.active>a:hover {
|
||||
color: #fff;
|
||||
background-color: #303ea1;
|
||||
border-bottom: 3px solid white;
|
||||
}
|
||||
|
||||
.navbar-form .form-control {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
/* SIDEBAR */
|
||||
|
||||
.toc .level1>li {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.toc .nav>li>a {
|
||||
color: #34393e;
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: #fff;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: #fff;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.toc-filter {
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toc-filter>input {
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.toc-filter>.filter-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidetoc>.toc {
|
||||
background-color: #fff;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sidetoc {
|
||||
background-color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* ALERTS */
|
||||
|
||||
.alert {
|
||||
padding: 0px 0px 5px 0px;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
box-shadow: 0px 2px 2px 0px rgba(100, 100, 100, 0.4);
|
||||
}
|
||||
|
||||
.alert>p {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.alert>ul {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 40px;
|
||||
}
|
||||
|
||||
.alert>h5 {
|
||||
padding: 10px 15px;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.alert-info>h5 {
|
||||
color: #1976d2;
|
||||
border-bottom: 4px solid #1976d2;
|
||||
background-color: #e3f2fd;
|
||||
}
|
||||
|
||||
.alert-warning>h5 {
|
||||
color: #f57f17;
|
||||
border-bottom: 4px solid #f57f17;
|
||||
background-color: #fff3e0;
|
||||
}
|
||||
|
||||
.alert-danger>h5 {
|
||||
color: #d32f2f;
|
||||
border-bottom: 4px solid #d32f2f;
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
/* CODE HIGHLIGHT */
|
||||
pre {
|
||||
padding: 9.5px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: #fffaef;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 1px 4px 1px rgba(100, 100, 100, 0.4);
|
||||
}
|
||||
|
||||
.sideaffix {
|
||||
overflow: visible;
|
||||
}
|
||||
+2
-2
@@ -18,5 +18,5 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("8.0.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.2.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.2.0")]
|
||||
[assembly: AssemblyFileVersion("8.3.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.3.0")]
|
||||
|
||||
@@ -67,31 +67,34 @@ namespace Umbraco.Core
|
||||
: new NamedSemaphoreReleaser(_semaphore2);
|
||||
}
|
||||
|
||||
public Task<IDisposable> LockAsync()
|
||||
{
|
||||
var wait = _semaphore != null
|
||||
? _semaphore.WaitAsync()
|
||||
: _semaphore2.WaitOneAsync();
|
||||
//NOTE: We don't use the "Async" part of this lock at all
|
||||
//TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again
|
||||
|
||||
return wait.IsCompleted
|
||||
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
: wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
|
||||
this, CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
//public Task<IDisposable> LockAsync()
|
||||
//{
|
||||
// var wait = _semaphore != null
|
||||
// ? _semaphore.WaitAsync()
|
||||
// : _semaphore2.WaitOneAsync();
|
||||
|
||||
public Task<IDisposable> LockAsync(int millisecondsTimeout)
|
||||
{
|
||||
var wait = _semaphore != null
|
||||
? _semaphore.WaitAsync(millisecondsTimeout)
|
||||
: _semaphore2.WaitOneAsync(millisecondsTimeout);
|
||||
// return wait.IsCompleted
|
||||
// ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
// : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
|
||||
// this, CancellationToken.None,
|
||||
// TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
//}
|
||||
|
||||
return wait.IsCompleted
|
||||
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
: wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
|
||||
this, CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
//public Task<IDisposable> LockAsync(int millisecondsTimeout)
|
||||
//{
|
||||
// var wait = _semaphore != null
|
||||
// ? _semaphore.WaitAsync(millisecondsTimeout)
|
||||
// : _semaphore2.WaitOneAsync(millisecondsTimeout);
|
||||
|
||||
// return wait.IsCompleted
|
||||
// ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
// : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
|
||||
// this, CancellationToken.None,
|
||||
// TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
//}
|
||||
|
||||
public IDisposable Lock()
|
||||
{
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
public const string TemplateFrontEndCacheKey = "template";
|
||||
|
||||
public const string MacroContentCacheKey = "macroContent_"; // used in MacroRenderers
|
||||
public const string MacroFromAliasCacheKey = "macroFromAlias_";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
|
||||
// register the IFileSystem supporting the IMediaFileSystem
|
||||
// THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM
|
||||
// and, SupportingFileSystem.For<IMediaFileSystem>() returns the underlying filesystem
|
||||
composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
|
||||
composition.SetMediaFileSystem(() => new PhysicalFileSystem(SystemDirectories.Media));
|
||||
|
||||
return composition;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
|
||||
var pluginLangFolders = appPlugins.Exists == false
|
||||
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
|
||||
: appPlugins.GetDirectories()
|
||||
.SelectMany(x => x.GetDirectories("Lang"))
|
||||
.SelectMany(x => x.GetDirectories("Lang", SearchOption.AllDirectories))
|
||||
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
|
||||
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
|
||||
|
||||
|
||||
@@ -12,31 +12,62 @@
|
||||
/// or MS.DI, PerDependency in Autofac.</remarks>
|
||||
Transient,
|
||||
|
||||
// TODO: We need to fix this up, currently LightInject is the only one that behaves differently from all other containers.
|
||||
// ... the simple fix would be to map this to PerScopeLifetime in LI but need to wait on a response here https://github.com/seesharper/LightInject/issues/494#issuecomment-518942625
|
||||
//
|
||||
// we use it for controllers, httpContextBase and other request scoped objects: MembershpHelper, TagQuery, UmbracoTreeSearcher and ISearchableTree
|
||||
// - so that they are automatically disposed at the end of the scope (ie request)
|
||||
// - not sure they should not be simply 'scoped'?
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per request.
|
||||
/// </summary>
|
||||
// TODO: review lifetimes for LightInject vs other containers
|
||||
// currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope'
|
||||
// but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped
|
||||
//
|
||||
// we use it for controllers, httpContextBase and umbracoContext
|
||||
// - so that they are automatically disposed at the end of the scope (ie request)
|
||||
// - not sure they should not be simply 'scoped'?
|
||||
//
|
||||
// Castle has an extra PerWebRequest something, and others use scope
|
||||
// what about Request before first request ie during application startup?
|
||||
// see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/
|
||||
// Castle ends up requiring a special scope manager too
|
||||
// see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4
|
||||
//
|
||||
// but maybe also - why are we requiring scoped services at startup?
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Any instance created with this lifetime will be disposed at the end of a request.
|
||||
/// </para>
|
||||
/// Corresponds to
|
||||
/// <para>
|
||||
/// PerRequestLifeTime in LightInject - means transient but disposed at the end of the current web request.
|
||||
/// see: https://github.com/seesharper/LightInject/issues/494#issuecomment-518493262
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Scoped in MS.DI - means one per web request.
|
||||
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
|
||||
/// <para>
|
||||
/// InstancePerRequest in Autofac - means one per web request.
|
||||
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
|
||||
/// But "Behind the scenes, though, it’s still just instance per matching lifetime scope."
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// LifestylePerWebRequest in Castle Windsor - means one per web request.
|
||||
/// see https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-part-7-lifestyles.md#the-perwebrequest-lifestyle
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Request,
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per container scope.
|
||||
/// One unique instance per scope.
|
||||
/// </summary>
|
||||
/// <remarks>Corresponds to Scope in LightInject, Scoped in MS.DI
|
||||
/// or Castle Windsor, PerLifetimeScope in Autofac.</remarks>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Any instance created with this lifetime will be disposed at the end of the current scope.
|
||||
/// </para>
|
||||
/// Corresponds to
|
||||
/// <para>PerScopeLifetime in LightInject (when in a request, means one per web request)</para>
|
||||
/// <para>
|
||||
/// Scoped in MS.DI (when in a request, means one per web request)
|
||||
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
|
||||
/// <para>
|
||||
/// InstancePerLifetimeScope in Autofac (when in a request, means one per web request)
|
||||
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-lifetime-scope
|
||||
/// Also note that Autofac's InstancePerRequest is the same as this, see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
|
||||
/// it says "Behind the scenes, though, it’s still just instance per matching lifetime scope."
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// LifestyleScoped in Castle Windsor
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Scope,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace Umbraco.Core.Configuration
|
||||
/// </summary>
|
||||
private static void ResetInternal()
|
||||
{
|
||||
GlobalSettingsExtensions.Reset();
|
||||
_reservedPaths = null;
|
||||
_reservedUrls = null;
|
||||
HasSmtpServer = null;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Routing;
|
||||
using Umbraco.Core.IO;
|
||||
@@ -9,22 +11,9 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public static class GlobalSettingsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in unit testing to reset all config items, this is automatically called by GlobalSettings.Reset()
|
||||
/// </summary>
|
||||
internal static void Reset()
|
||||
{
|
||||
_reservedUrlsCache = null;
|
||||
_mvcArea = null;
|
||||
}
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
//make this volatile so that we can ensure thread safety with a double check lock
|
||||
private static volatile string _reservedUrlsCache;
|
||||
private static string _reservedPathsCache;
|
||||
private static HashSet<string> _reservedList = new HashSet<string>();
|
||||
private static string _mvcArea;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This returns the string of the MVC Area route.
|
||||
/// </summary>
|
||||
@@ -40,6 +29,13 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
if (_mvcArea != null) return _mvcArea;
|
||||
|
||||
_mvcArea = GetUmbracoMvcAreaNoCache(globalSettings);
|
||||
|
||||
return _mvcArea;
|
||||
}
|
||||
|
||||
internal static string GetUmbracoMvcAreaNoCache(this IGlobalSettings globalSettings)
|
||||
{
|
||||
if (globalSettings.Path.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified");
|
||||
@@ -48,95 +44,8 @@ namespace Umbraco.Core.Configuration
|
||||
var path = globalSettings.Path;
|
||||
if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518
|
||||
path = path.Substring(SystemDirectories.Root.Length);
|
||||
_mvcArea = path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
|
||||
return _mvcArea;
|
||||
return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="url">The URL to check.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url)
|
||||
{
|
||||
if (_reservedUrlsCache == null)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (_reservedUrlsCache == null)
|
||||
{
|
||||
// store references to strings to determine changes
|
||||
_reservedPathsCache = globalSettings.ReservedPaths;
|
||||
_reservedUrlsCache = globalSettings.ReservedUrls;
|
||||
|
||||
// add URLs and paths to a new list
|
||||
var newReservedList = new HashSet<string>();
|
||||
foreach (var reservedUrlTrimmed in _reservedUrlsCache
|
||||
.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim().ToLowerInvariant())
|
||||
.Where(x => x.IsNullOrWhiteSpace() == false)
|
||||
.Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/"))
|
||||
.Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false))
|
||||
{
|
||||
newReservedList.Add(reservedUrlTrimmed);
|
||||
}
|
||||
|
||||
foreach (var reservedPathTrimmed in _reservedPathsCache
|
||||
.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim().ToLowerInvariant())
|
||||
.Where(x => x.IsNullOrWhiteSpace() == false)
|
||||
.Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/"))
|
||||
.Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false))
|
||||
{
|
||||
newReservedList.Add(reservedPathTrimmed);
|
||||
}
|
||||
|
||||
// use the new list from now on
|
||||
_reservedList = newReservedList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//The url should be cleaned up before checking:
|
||||
// * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
|
||||
// * We shouldn't be comparing the query at all
|
||||
var pathPart = url.Split(new[] {'?'}, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant();
|
||||
if (pathPart.Contains(".") == false)
|
||||
{
|
||||
pathPart = pathPart.EnsureEndsWith('/');
|
||||
}
|
||||
|
||||
// return true if url starts with an element of the reserved list
|
||||
return _reservedList.Any(x => pathPart.InvariantStartsWith(x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current request is reserved based on the route table and
|
||||
/// whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="routes">The route collection to lookup the request in</param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url, HttpContextBase httpContext, RouteCollection routes)
|
||||
{
|
||||
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
|
||||
if (routes == null) throw new ArgumentNullException(nameof(routes));
|
||||
|
||||
//check if the current request matches a route, if so then it is reserved.
|
||||
//TODO: This value should be cached! Else this is doing double routing in MVC every request!
|
||||
var route = routes.GetRouteData(httpContext);
|
||||
if (route != null)
|
||||
return true;
|
||||
|
||||
//continue with the standard ignore routine
|
||||
return globalSettings.IsReservedPathOrUrl(url);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Umbraco.Core.Configuration.Grid
|
||||
public interface IGridEditorConfig
|
||||
{
|
||||
string Name { get; }
|
||||
string NameTemplate { get; }
|
||||
string Alias { get; }
|
||||
string View { get; }
|
||||
string Render { get; }
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class ContentElement : UmbracoConfigurationElement, IContentSection
|
||||
{
|
||||
private const string DefaultPreviewBadge = @"<a id=""umbracoPreviewBadge"" style=""z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;"" href=""#"" OnClick=""javascript:window.top.location.href = '{0}/preview/end?redir={1}'""><span style=""display:none;"">In Preview Mode - click to end</span></a>";
|
||||
private const string DefaultPreviewBadge = @"<div id=""umbracoPreviewBadge"" class=""umbraco-preview-badge""><span class=""umbraco-preview-badge__header"">Preview mode</span><a href=""{0}/preview/end?redir={1}"" class=""umbraco-preview-badge__end""><svg viewBox=""0 0 100 100"" xmlns=""http://www.w3.org/2000/svg""><title>Click to end</title><path d=""M5273.1 2400.1v-2c0-2.8-5-4-9.7-4s-9.7 1.3-9.7 4v2a7 7 0 002 4.9l5 4.9c.3.3.4.6.4 1v6.4c0 .4.2.7.6.8l2.9.9c.5.1 1-.2 1-.8v-7.2c0-.4.2-.7.4-1l5.1-5a7 7 0 002-4.9zm-9.7-.1c-4.8 0-7.4-1.3-7.5-1.8.1-.5 2.7-1.8 7.5-1.8s7.3 1.3 7.5 1.8c-.2.5-2.7 1.8-7.5 1.8z""/><path d=""M5268.4 2410.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1h-4.3zM5272.7 2413.7h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1zM5272.7 2417h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1 0-.5-.4-1-1-1z""/><path d=""M78.2 13l-8.7 11.7a32.5 32.5 0 11-51.9 25.8c0-10.3 4.7-19.7 12.9-25.8L21.8 13a47 47 0 1056.4 0z""/><path d=""M42.7 2.5h14.6v49.4H42.7z""/></svg></a></div><style type=""text/css"">.umbraco-preview-badge {{position: absolute;top: 1em;right: 1em;display: inline-flex;background: #1b264f;color: #fff;padding: 1em;font-size: 12px;z-index: 99999999;justify-content: center;align-items: center;box-shadow: 0 10px 50px rgba(0, 0, 0, .1), 0 6px 20px rgba(0, 0, 0, .16);line-height: 1;}}.umbraco-preview-badge__header {{font-weight: bold;}}.umbraco-preview-badge__end {{width: 3em;padding: 1em;margin: -1em -1em -1em 2em;display: flex;flex-shrink: 0;align-items: center;align-self: stretch;}}.umbraco-preview-badge__end:hover,.umbraco-preview-badge__end:focus {{background: #f5c1bc;}}.umbraco-preview-badge__end svg {{fill: #fff;width:1em;}}</style>";
|
||||
|
||||
[ConfigurationProperty("imaging")]
|
||||
internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"];
|
||||
|
||||
@@ -14,6 +14,23 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public const string InternalGenericPropertiesPrefix = "_umb_";
|
||||
|
||||
public static class Legacy
|
||||
{
|
||||
public static class Aliases
|
||||
{
|
||||
public const string Textbox = "Umbraco.Textbox";
|
||||
public const string Date = "Umbraco.Date";
|
||||
public const string ContentPicker2 = "Umbraco.ContentPicker2";
|
||||
public const string MediaPicker2 = "Umbraco.MediaPicker2";
|
||||
public const string MemberPicker2 = "Umbraco.MemberPicker2";
|
||||
public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2";
|
||||
public const string TextboxMultiple = "Umbraco.TextboxMultiple";
|
||||
public const string RelatedLinks2 = "Umbraco.RelatedLinks2";
|
||||
public const string RelatedLinks = "Umbraco.RelatedLinks";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines Umbraco built-in property editor aliases.
|
||||
/// </summary>
|
||||
@@ -68,17 +85,17 @@ namespace Umbraco.Core
|
||||
/// ListView.
|
||||
/// </summary>
|
||||
public const string ListView = "Umbraco.ListView";
|
||||
|
||||
/// <summary>
|
||||
/// Macro Container.
|
||||
/// </summary>
|
||||
public const string MacroContainer = "Umbraco.MacroContainer";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Media Picker.
|
||||
/// </summary>
|
||||
public const string MediaPicker = "Umbraco.MediaPicker";
|
||||
|
||||
/// <summary>
|
||||
/// Multiple Media Picker.
|
||||
/// </summary>
|
||||
public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker";
|
||||
|
||||
/// <summary>
|
||||
/// Member Picker.
|
||||
/// </summary>
|
||||
@@ -186,6 +203,24 @@ namespace Umbraco.Core
|
||||
/// <remarks>Must be a valid <see cref="ValueTypes"/> value.</remarks>
|
||||
public const string DataValueType = "umbracoDataValueType";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines Umbraco's built-in property editor groups.
|
||||
/// </summary>
|
||||
public static class Groups
|
||||
{
|
||||
public const string Common = "Common";
|
||||
|
||||
public const string Lists = "Lists";
|
||||
|
||||
public const string Media = "Media";
|
||||
|
||||
public const string People = "People";
|
||||
|
||||
public const string Pickers = "Pickers";
|
||||
|
||||
public const string RichContent = "Rich Content";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,39 +150,46 @@ namespace Umbraco.Core
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
segment = segment.NullOrWhiteSpaceAsNull();
|
||||
|
||||
bool Validate(bool variesBy, string value)
|
||||
{
|
||||
if (variesBy)
|
||||
{
|
||||
// varies by
|
||||
// in exact mode, the value cannot be null (but it can be a wildcard)
|
||||
// in !wildcards mode, the value cannot be a wildcard (but it can be null)
|
||||
if ((exact && value == null) || (!wildcards && value == "*"))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// does not vary by value
|
||||
// the value cannot have a value
|
||||
// unless wildcards and it's "*"
|
||||
if (value != null && (!wildcards || value != "*"))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Validate(variation.VariesByCulture(), culture))
|
||||
// if wildcards are disabled, do not allow "*"
|
||||
if (!wildcards && (culture == "*" || segment == "*"))
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Culture value \"{culture ?? "<null>"}\" is invalid.");
|
||||
throw new NotSupportedException($"Variation wildcards are not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Validate(variation.VariesBySegment(), segment))
|
||||
if (variation.VariesByCulture())
|
||||
{
|
||||
// varies by culture
|
||||
// in exact mode, the culture cannot be null
|
||||
if (exact && culture == null)
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Culture may not be null because culture variation is enabled.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// does not vary by culture
|
||||
// the culture cannot have a value
|
||||
// unless wildcards and it's "*"
|
||||
if (culture != null && !(wildcards && culture == "*"))
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if it does not vary by segment
|
||||
// the segment cannot have a value
|
||||
// segment may always be null, even when the ContentVariation.Segment flag is set for this variation,
|
||||
// therefore the exact parameter is not used in segment validation.
|
||||
if (!variation.VariesBySegment() && segment != null && !(wildcards && segment == "*"))
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Segment value \"{segment ?? "<null>"}\" is invalid.");
|
||||
throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -15,7 +16,7 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static string ToIsoString(this DateTime dt)
|
||||
{
|
||||
return dt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
return dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an internal exception that in theory should never been thrown, it is only thrown in circumstances that should never happen.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Exception" />
|
||||
[Serializable]
|
||||
internal class PanicException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PanicException" /> class.
|
||||
/// </summary>
|
||||
public PanicException()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PanicException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public PanicException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PanicException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
|
||||
public PanicException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PanicException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
|
||||
protected PanicException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public static string TempFileUploads => TempData + "/FileUploads";
|
||||
|
||||
public static string TempImageUploads => TempFileUploads + "/rte";
|
||||
|
||||
public static string Install => "~/install";
|
||||
|
||||
public static string AppCode => "~/App_Code";
|
||||
|
||||
@@ -42,6 +42,12 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
|
||||
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Serilog minimum log level
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetLogLevel();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the collection of logs
|
||||
/// </summary>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
@@ -89,6 +92,16 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
return errorCounter.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetLogLevel()
|
||||
{
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.Logger.IsEnabled)?.Min() ?? null;
|
||||
return logLevel?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var counter = new CountingFilter();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -64,7 +65,7 @@ namespace Umbraco.Core
|
||||
// a new process for the same application path
|
||||
|
||||
var appPath = HostingEnvironment.ApplicationPhysicalPath;
|
||||
var hash = (appId + ":::" + appPath).ToSHA1();
|
||||
var hash = (appId + ":::" + appPath).GenerateHash<SHA1>();
|
||||
|
||||
var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK";
|
||||
_asyncLock = new AsyncLock(lockName);
|
||||
@@ -113,7 +114,7 @@ namespace Umbraco.Core
|
||||
|
||||
lock (_locko)
|
||||
{
|
||||
_logger.Debug<MainDom>("Signaled {Signaled} ({SignalSource})", _signaled ? "(again)" : string.Empty, source);
|
||||
_logger.Debug<MainDom>("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source);
|
||||
if (_signaled) return;
|
||||
if (_isMainDom == false) return; // probably not needed
|
||||
_signaled = true;
|
||||
@@ -171,6 +172,7 @@ namespace Umbraco.Core
|
||||
// if more than 1 instance reach that point, one will get the lock
|
||||
// and the other one will timeout, which is accepted
|
||||
|
||||
//TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset?
|
||||
_asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds);
|
||||
_isMainDom = true;
|
||||
|
||||
@@ -181,6 +183,9 @@ namespace Umbraco.Core
|
||||
// which is accepted
|
||||
|
||||
_signal.Reset();
|
||||
|
||||
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
|
||||
|
||||
_signal.WaitOneAsync()
|
||||
.ContinueWith(_ => OnSignal("signal"));
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Mapping
|
||||
{
|
||||
@@ -231,10 +232,10 @@ namespace Umbraco.Core.Mapping
|
||||
if (ctor != null && map != null)
|
||||
{
|
||||
// register (for next time) and do it now (for this time)
|
||||
object NCtor(object s, MapperContext c) => MapEnumerableInternal<TTarget>((IEnumerable) s, targetGenericArg, ctor, map, c);
|
||||
object NCtor(object s, MapperContext c) => MapEnumerableInternal<TTarget>((IEnumerable)s, targetGenericArg, ctor, map, c);
|
||||
DefineCtors(sourceType)[targetType] = NCtor;
|
||||
DefineMaps(sourceType)[targetType] = Identity;
|
||||
return (TTarget) NCtor(source, context);
|
||||
return (TTarget)NCtor(source, context);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Don't know how to map {sourceGenericArg.FullName} to {targetGenericArg.FullName}, so don't know how to map {sourceType.FullName} to {targetType.FullName}.");
|
||||
@@ -259,13 +260,13 @@ namespace Umbraco.Core.Mapping
|
||||
if (typeof(TTarget).IsArray)
|
||||
{
|
||||
var elementType = typeof(TTarget).GetElementType();
|
||||
if (elementType == null) throw new Exception("panic");
|
||||
if (elementType == null) throw new PanicException("elementType == null which should never occur");
|
||||
var targetArray = Array.CreateInstance(elementType, targetList.Count);
|
||||
targetList.CopyTo(targetArray, 0);
|
||||
target = targetArray;
|
||||
}
|
||||
|
||||
return (TTarget) target;
|
||||
return (TTarget)target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -342,7 +343,21 @@ namespace Umbraco.Core.Mapping
|
||||
|
||||
if (ctor == null) return null;
|
||||
|
||||
_ctors[sourceType] = sourceCtor;
|
||||
_ctors.AddOrUpdate(sourceType, sourceCtor, (k, v) =>
|
||||
{
|
||||
// Add missing constructors
|
||||
foreach (var c in sourceCtor)
|
||||
{
|
||||
if (!v.ContainsKey(c.Key))
|
||||
{
|
||||
v.Add(c.Key, c.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
});
|
||||
|
||||
|
||||
return ctor;
|
||||
}
|
||||
|
||||
@@ -367,7 +382,17 @@ namespace Umbraco.Core.Mapping
|
||||
|
||||
if (map == null) return null;
|
||||
|
||||
_maps[sourceType] = sourceMap;
|
||||
if (_maps.ContainsKey(sourceType))
|
||||
{
|
||||
foreach (var m in sourceMap)
|
||||
{
|
||||
if (!_maps[sourceType].TryGetValue(m.Key, out _))
|
||||
_maps[sourceType].Add(m.Key, m.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
_maps[sourceType] = sourceMap;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -382,7 +407,7 @@ namespace Umbraco.Core.Mapping
|
||||
{
|
||||
if (type.IsArray) return type.GetElementType();
|
||||
if (type.IsGenericType) return type.GenericTypeArguments[0];
|
||||
throw new Exception("panic");
|
||||
throw new PanicException($"Could not get enumerable or array type from {type}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+36
-8
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Migrations.Expressions.Common;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
@@ -27,31 +29,57 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes
|
||||
{
|
||||
_context.BuildingExpression = false;
|
||||
|
||||
//get a list of all constraints - this will include all PK, FK and unique constraints
|
||||
var tableConstraints = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
|
||||
|
||||
//get a list of defined indexes - this will include all indexes, unique indexes and unique constraint indexes
|
||||
var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
|
||||
|
||||
var uniqueConstraintNames = tableConstraints.Where(x => !x.Item2.InvariantStartsWith("PK_") && !x.Item2.InvariantStartsWith("FK_")).Select(x => x.Item2);
|
||||
var indexNames = indexes.Select(x => x.IndexName).ToList();
|
||||
|
||||
// drop keys
|
||||
if (DeleteLocal || DeleteForeign)
|
||||
{
|
||||
// table, constraint
|
||||
var tableKeys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
|
||||
|
||||
if (DeleteForeign)
|
||||
{
|
||||
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_")))
|
||||
//In some cases not all FK's are prefixed with "FK" :/ mostly with old upgraded databases so we need to check if it's either:
|
||||
// * starts with FK OR
|
||||
// * doesn't start with PK_ and doesn't exist in the list of indexes
|
||||
|
||||
foreach (var key in tableConstraints.Where(x => x.Item1 == TableName
|
||||
&& (x.Item2.InvariantStartsWith("FK_") || (!x.Item2.InvariantStartsWith("PK_") && !indexNames.InvariantContains(x.Item2)))))
|
||||
{
|
||||
Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do();
|
||||
}
|
||||
|
||||
}
|
||||
if (DeleteLocal)
|
||||
{
|
||||
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_")))
|
||||
foreach (var key in tableConstraints.Where(x => x.Item1 == TableName && x.Item2.InvariantStartsWith("PK_")))
|
||||
Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do();
|
||||
|
||||
// note: we do *not* delete the DEFAULT constraints
|
||||
// note: we do *not* delete the DEFAULT constraints and if we wanted to we'd have to deal with that in interesting ways
|
||||
// since SQL server has a specific way to handle that, see SqlServerSyntaxProvider.GetDefaultConstraintsPerColumn
|
||||
}
|
||||
}
|
||||
|
||||
// drop indexes
|
||||
if (DeleteLocal)
|
||||
{
|
||||
var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
|
||||
{
|
||||
foreach (var index in indexes.Where(x => x.TableName == TableName))
|
||||
Delete.Index(index.IndexName).OnTable(index.TableName).Do();
|
||||
{
|
||||
//if this is a unique constraint we need to drop the constraint, else drop the index
|
||||
//to figure this out, the index must be tagged as unique and it must exist in the tableConstraints
|
||||
|
||||
if (index.IsUnique && uniqueConstraintNames.InvariantContains(index.IndexName))
|
||||
Delete.UniqueConstraint(index.IndexName).FromTable(index.TableName).Do();
|
||||
else
|
||||
Delete.Index(index.IndexName).OnTable(index.TableName).Do();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,6 +190,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings });
|
||||
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users });
|
||||
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms });
|
||||
_database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Translation });
|
||||
|
||||
_database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content });
|
||||
|
||||
@@ -226,8 +227,8 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
@@ -71,10 +72,21 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
// flip known property types
|
||||
|
||||
var intPropertyTypes = new[] { 7, 8, 29 };
|
||||
var bigintPropertyTypes = new[] { 9, 26 };
|
||||
var dtPropertyTypes = new[] { 32, 33, 34 };
|
||||
var labelPropertyTypes = Database.Fetch<PropertyTypeDto>(Sql()
|
||||
.Select<PropertyTypeDto>(x => x.Id, x => x.Alias)
|
||||
.From<PropertyTypeDto>()
|
||||
.Where<PropertyTypeDto>(x => x.DataTypeId == Constants.DataTypes.LabelString)
|
||||
);
|
||||
|
||||
var intPropertyAliases = new[] { Constants.Conventions.Media.Width, Constants.Conventions.Media.Height, Constants.Conventions.Member.FailedPasswordAttempts };
|
||||
var bigintPropertyAliases = new[] { Constants.Conventions.Media.Bytes };
|
||||
var dtPropertyAliases = new[] { Constants.Conventions.Member.LastLockoutDate, Constants.Conventions.Member.LastLoginDate, Constants.Conventions.Member.LastPasswordChangeDate };
|
||||
|
||||
var intPropertyTypes = labelPropertyTypes.Where(pt => intPropertyAliases.Contains(pt.Alias)).Select(pt => pt.Id).ToArray();
|
||||
var bigintPropertyTypes = labelPropertyTypes.Where(pt => bigintPropertyAliases.Contains(pt.Alias)).Select(pt => pt.Id).ToArray();
|
||||
var dtPropertyTypes = labelPropertyTypes.Where(pt => dtPropertyAliases.Contains(pt.Alias)).Select(pt => pt.Id).ToArray();
|
||||
|
||||
Database.Execute(Sql().Update<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelInt)).WhereIn<PropertyTypeDto>(x => x.Id, intPropertyTypes));
|
||||
Database.Execute(Sql().Update<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelInt)).WhereIn<PropertyTypeDto>(x => x.Id, intPropertyTypes));
|
||||
Database.Execute(Sql().Update<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelBigint)).WhereIn<PropertyTypeDto>(x => x.Id, bigintPropertyTypes));
|
||||
Database.Execute(Sql().Update<PropertyTypeDto>(u => u.Set(x => x.DataTypeId, Constants.DataTypes.LabelDateTime)).WhereIn<PropertyTypeDto>(x => x.Id, dtPropertyTypes));
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var sqlDataTypes = Sql()
|
||||
.Select<DataTypeDto>()
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == "Umbraco.RelatedLinks"
|
||||
|| x.EditorAlias == "Umbraco.RelatedLinks2");
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks
|
||||
|| x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2);
|
||||
|
||||
var dataTypes = Database.Fetch<DataTypeDto>(sqlDataTypes);
|
||||
var dataTypeIds = dataTypes.Select(x => x.NodeId).ToList();
|
||||
@@ -72,6 +72,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
var sqlNodeData = Sql()
|
||||
.Select<NodeDto>()
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeId == intId);
|
||||
|
||||
var node = Database.Fetch<NodeDto>(sqlNodeData).FirstOrDefault();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -18,6 +19,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private static readonly ISet<string> LegacyAliases = new HashSet<string>()
|
||||
{
|
||||
Constants.PropertyEditors.Legacy.Aliases.Date,
|
||||
Constants.PropertyEditors.Legacy.Aliases.Textbox,
|
||||
Constants.PropertyEditors.Legacy.Aliases.ContentPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MemberPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2,
|
||||
};
|
||||
|
||||
public DataTypeMigration(IMigrationContext context, PreValueMigratorCollection preValueMigrators, PropertyEditorCollection propertyEditors, ILogger logger)
|
||||
: base(context)
|
||||
{
|
||||
@@ -61,25 +74,41 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(x => x.NodeId == group.Key)).First();
|
||||
|
||||
// check for duplicate aliases
|
||||
var aliases = group.Select(x => x.Alias).Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
|
||||
if (aliases.Distinct().Count() != aliases.Length)
|
||||
throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataType.NodeId}, editor={dataType.EditorAlias}: duplicate alias.");
|
||||
|
||||
// handle null/empty aliases
|
||||
int index = 0;
|
||||
var dictionary = group.ToDictionary(x => string.IsNullOrWhiteSpace(x.Alias) ? index++.ToString() : x.Alias);
|
||||
|
||||
// migrate the preValues to configuration
|
||||
var migrator = _preValueMigrators.GetMigrator(dataType.EditorAlias) ?? new DefaultPreValueMigrator();
|
||||
var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, group.ToDictionary(x => x.Alias, x => x));
|
||||
var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, dictionary);
|
||||
var json = JsonConvert.SerializeObject(config);
|
||||
|
||||
// validate - and kill the migration if it fails
|
||||
var newAlias = migrator.GetNewAlias(dataType.EditorAlias);
|
||||
if (newAlias == null)
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, dataType.EditorAlias);
|
||||
if (!LegacyAliases.Contains(dataType.EditorAlias))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>(
|
||||
"Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, dataType.EditorAlias);
|
||||
}
|
||||
}
|
||||
else if (!_propertyEditors.TryGet(newAlias, out var propertyEditor))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
|
||||
+ " because no property editor with that alias was found."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, newAlias, dataType.EditorAlias);
|
||||
if (!LegacyAliases.Contains(newAlias))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
|
||||
+ " because no property editor with that alias was found."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, newAlias, dataType.EditorAlias);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
class ContentPickerPreValueMigrator : DefaultPreValueMigrator
|
||||
{
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> editorAlias == "Umbraco.ContentPicker2";
|
||||
=> editorAlias == Constants.PropertyEditors.Legacy.Aliases.ContentPicker2;
|
||||
|
||||
public override string GetNewAlias(string editorAlias)
|
||||
=> null;
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
}
|
||||
|
||||
// assuming we don't want to fall back to array
|
||||
if (aliases.Length != preValuesA.Count || aliases.Any(string.IsNullOrWhiteSpace))
|
||||
throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataTypeId} preValues: duplicate or null/empty alias.");
|
||||
if (aliases.Any(string.IsNullOrWhiteSpace))
|
||||
throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataTypeId}, editor={editorAlias}: null/empty alias.");
|
||||
|
||||
// dictionary-base prevalues
|
||||
return GetPreValues(preValuesA).ToDictionary(x => x.Alias, GetPreValueValue);
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
class MarkdownEditorPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase
|
||||
{
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> editorAlias == Constants.PropertyEditors.Aliases.MarkdownEditor;
|
||||
|
||||
protected override object GetPreValueValue(PreValueDto preValue)
|
||||
{
|
||||
if (preValue.Alias == "preview")
|
||||
return preValue.Value == "1";
|
||||
|
||||
return base.GetPreValueValue(preValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,15 +6,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
private readonly string[] _editors =
|
||||
{
|
||||
"Umbraco.MediaPicker2",
|
||||
"Umbraco.MediaPicker"
|
||||
Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
|
||||
Constants.PropertyEditors.Aliases.MediaPicker
|
||||
};
|
||||
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> _editors.Contains(editorAlias);
|
||||
|
||||
public override string GetNewAlias(string editorAlias)
|
||||
=> "Umbraco.MediaPicker";
|
||||
=> Constants.PropertyEditors.Aliases.MediaPicker;
|
||||
|
||||
// you wish - but MediaPickerConfiguration lives in Umbraco.Web
|
||||
/*
|
||||
|
||||
@@ -20,7 +20,8 @@ public class PreValueMigratorComposer : ICoreComposer
|
||||
.Append<DecimalPreValueMigrator>()
|
||||
.Append<ListViewPreValueMigrator>()
|
||||
.Append<DropDownFlexiblePreValueMigrator>()
|
||||
.Append<ValueListPreValueMigrator>();
|
||||
.Append<ValueListPreValueMigrator>()
|
||||
.Append<MarkdownEditorPreValueMigrator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
case "Umbraco.NoEdit":
|
||||
return Constants.PropertyEditors.Aliases.Label;
|
||||
default:
|
||||
throw new Exception("panic");
|
||||
throw new PanicException($"The alias {editorAlias} is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
private readonly string[] _editors =
|
||||
{
|
||||
"Umbraco.RadioButtonList",
|
||||
"Umbraco.CheckBoxList",
|
||||
"Umbraco.DropDown",
|
||||
"Umbraco.DropdownlistPublishingKeys",
|
||||
"Umbraco.DropDownMultiple",
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var dataTypes = GetDataTypes("Umbraco.Date");
|
||||
var dataTypes = GetDataTypes(Constants.PropertyEditors.Legacy.Aliases.Date);
|
||||
|
||||
foreach (var dataType in dataTypes)
|
||||
{
|
||||
@@ -25,6 +25,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
config = (DateTimeConfiguration) new CustomDateTimeConfigurationEditor().FromDatabase(
|
||||
dataType.Configuration);
|
||||
|
||||
// If the Umbraco.Date type is the default from V7 and it has never been updated, then the
|
||||
// configuration is empty, and the format stuff is handled by in JS by moment.js. - We can't do that
|
||||
// after the migration, so we force the format to the default from V7.
|
||||
if (string.IsNullOrEmpty(dataType.Configuration))
|
||||
{
|
||||
config.Format = "YYYY-MM-DD";
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.ContentPicker + "2", Constants.PropertyEditors.Aliases.ContentPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
|
||||
RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false);
|
||||
RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, Constants.PropertyEditors.Aliases.ContentPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, Constants.PropertyEditors.Aliases.MediaPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, Constants.PropertyEditors.Aliases.MemberPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, Constants.PropertyEditors.Aliases.TextArea, false);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.Textbox, Constants.PropertyEditors.Aliases.TextBox, false);
|
||||
}
|
||||
|
||||
private void RenameDataType(string fromAlias, string toAlias, bool checkCollision = true)
|
||||
@@ -43,7 +43,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
$"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " +
|
||||
$"to the non-obsolete v7 property editors of the same type.");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Database.Execute(Sql()
|
||||
|
||||
@@ -15,21 +15,17 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
//special trick to add the column without constraints and return the sql to add them later
|
||||
AddColumn<MacroDto>("macroType", out var sqls1);
|
||||
//now we need to update the new column with some values because this column doesn't allow NULL values
|
||||
Update.Table(Constants.DatabaseSchema.Tables.Macro).Set(new { macroType = (int)MacroTypes.Unknown}).AllRows().Do();
|
||||
//now apply constraints (NOT NULL) to new table
|
||||
foreach (var sql in sqls1) Execute.Sql(sql).Do();
|
||||
|
||||
//special trick to add the column without constraints and return the sql to add them later
|
||||
AddColumn<MacroDto>("macroSource", out var sqls2);
|
||||
|
||||
//populate the new macroSource column with legacy data
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython IS NOT NULL").Do();
|
||||
//populate the new columns with legacy data
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = '', macroType = {(int)MacroTypes.Unknown}").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT != '' AND macroXSLT IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly != '' AND macroScriptAssembly IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType != '' AND macroScriptType IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython != '' AND macroPython IS NOT NULL").Do();
|
||||
|
||||
//now apply constraints (NOT NULL) to new table
|
||||
foreach (var sql in sqls1) Execute.Sql(sql).Do();
|
||||
foreach (var sql in sqls2) Execute.Sql(sql).Do();
|
||||
|
||||
//now remove these old columns
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
Database.Execute("set identity_insert umbracoUser off;");
|
||||
|
||||
Database.Execute("update umbracoUser2UserGroup set userId=-1 where userId=0;");
|
||||
Database.Execute("update umbracoUser2NodeNotify set userId=-1 where userId=0;");
|
||||
Database.Execute("update umbracoNode set nodeUser=-1 where nodeUser=0;");
|
||||
Database.Execute("update umbracoUserLogin set userId=-1 where userId=0;");
|
||||
Database.Execute($"update {Constants.DatabaseSchema.Tables.ContentVersion} set userId=-1 where userId=0;");
|
||||
|
||||
+31
-8
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
@@ -36,6 +37,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
|
||||
var properties = Database.Fetch<PropertyDataDto>(sqlPropertyData);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var value = property.TextValue;
|
||||
@@ -43,19 +45,34 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
|
||||
if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid)
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c))
|
||||
try
|
||||
{
|
||||
var controlValue = control["value"];
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c).OfType<JObject>())
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
var controlValue = control["value"];
|
||||
if (controlValue?.Type == JTokenType.String)
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
property.TextValue = JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
exceptions.Add(new InvalidOperationException(
|
||||
"Cannot deserialize the value as json. This can be because the property editor " +
|
||||
"type is changed from another type into a grid. Old versions of the value in this " +
|
||||
"property can have the structure from the old property editor type. This needs to be " +
|
||||
"changed manually before updating the database.\n" +
|
||||
$"Property info: Id = {property.Id}, LanguageId = {property.LanguageId}, VersionId = {property.VersionId}, Value = {property.Value}"
|
||||
, e));
|
||||
continue;
|
||||
}
|
||||
|
||||
property.TextValue = JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -65,6 +82,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
Database.Update(property);
|
||||
}
|
||||
|
||||
|
||||
if (exceptions.Any())
|
||||
{
|
||||
throw new AggregateException("One or more errors related to unexpected data in grid values occurred.", exceptions);
|
||||
}
|
||||
|
||||
Context.AddPostMigration<RebuildPublishedSnapshot>();
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,12 @@ namespace Umbraco.Core.Models.ContentEditing
|
||||
/// </remarks>
|
||||
[DataMember(Name = "active")]
|
||||
public bool Active { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content app badge.
|
||||
/// </summary>
|
||||
[DataMember(Name = "badge")]
|
||||
public ContentAppBadge Badge { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Umbraco.Core.Models.ContentEditing
|
||||
{
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
using Umbraco.Core.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a content app badge
|
||||
/// </summary>
|
||||
[DataContract(Name = "badge", Namespace = "")]
|
||||
public class ContentAppBadge
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentAppBadge"/> class.
|
||||
/// </summary>
|
||||
public ContentAppBadge()
|
||||
{
|
||||
this.Type = ContentAppBadgeType.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number displayed in the badge
|
||||
/// </summary>
|
||||
[DataMember(Name = "count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of badge to display
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This controls the background color of the badge.</para>
|
||||
/// <para>Warning will display a dark yellow badge</para>
|
||||
/// <para>Alert will display a red badge</para>
|
||||
/// <para>Default will display a turquoise badge</para>
|
||||
/// </remarks>
|
||||
[DataMember(Name = "type")]
|
||||
public ContentAppBadgeType Type { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Umbraco.Core.Models.ContentEditing
|
||||
{
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Represent the content app badge types
|
||||
/// </summary>
|
||||
[DataContract(Name = "contentAppBadgeType")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum ContentAppBadgeType
|
||||
{
|
||||
[EnumMember(Value = "default")]
|
||||
Default = 0,
|
||||
|
||||
[EnumMember(Value = "warning")]
|
||||
Warning = 1,
|
||||
|
||||
[EnumMember(Value = "alert")]
|
||||
Alert = 2
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,13 @@ namespace Umbraco.Core.Models
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void UnpublishCulture(this IContent content, string culture = "*")
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool UnpublishCulture(this IContent content, string culture = "*")
|
||||
{
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
@@ -229,16 +235,31 @@ namespace Umbraco.Core.Models
|
||||
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
|
||||
|
||||
if (culture == "*") // all cultures
|
||||
|
||||
var keepProcessing = true;
|
||||
|
||||
if (culture == "*")
|
||||
{
|
||||
// all cultures
|
||||
content.ClearPublishInfos();
|
||||
else // one single culture
|
||||
content.ClearPublishInfo(culture);
|
||||
}
|
||||
else
|
||||
{
|
||||
// one single culture
|
||||
keepProcessing = content.ClearPublishInfo(culture);
|
||||
}
|
||||
|
||||
|
||||
// property.PublishValues only publishes what is valid, variation-wise
|
||||
foreach (var property in content.Properties)
|
||||
property.UnpublishValues(culture);
|
||||
if (keepProcessing)
|
||||
{
|
||||
// property.PublishValues only publishes what is valid, variation-wise
|
||||
foreach (var property in content.Properties)
|
||||
property.UnpublishValues(culture);
|
||||
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
}
|
||||
|
||||
return keepProcessing;
|
||||
}
|
||||
|
||||
public static void ClearPublishInfos(this IContent content)
|
||||
@@ -246,15 +267,24 @@ namespace Umbraco.Core.Models
|
||||
content.PublishCultureInfos = null;
|
||||
}
|
||||
|
||||
public static void ClearPublishInfo(this IContent content, string culture)
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClearPublishInfo(this IContent content, string culture)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture));
|
||||
|
||||
content.PublishCultureInfos.Remove(culture);
|
||||
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCulture(culture);
|
||||
var removed = content.PublishCultureInfos.Remove(culture);
|
||||
if (removed)
|
||||
{
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCulture(culture);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
public interface IMemberEntitySlim : IContentEntitySlim
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
public class MemberEntitySlim : EntitySlim, IMemberEntitySlim
|
||||
{
|
||||
public string ContentTypeAlias { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentTypeIcon { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentTypeThumbnail { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
|
||||
PropertyGroupCollection PropertyGroups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all local property types belonging to a group, across all local property groups.
|
||||
/// Gets all local property types all local property groups or ungrouped.
|
||||
/// </summary>
|
||||
IEnumerable<PropertyType> PropertyTypes { get; }
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ namespace Umbraco.Core.Models.Membership
|
||||
return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections, group.Permissions);
|
||||
}
|
||||
|
||||
public static bool IsSystemUserGroup(this IUserGroup group) =>
|
||||
IsSystemUserGroup(group.Alias);
|
||||
|
||||
public static bool IsSystemUserGroup(this IReadOnlyUserGroup group) =>
|
||||
IsSystemUserGroup(group.Alias);
|
||||
|
||||
public static IReadOnlyUserGroup ToReadOnlyGroup(this UserGroupDto group)
|
||||
{
|
||||
return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon,
|
||||
@@ -22,5 +28,12 @@ namespace Umbraco.Core.Models.Membership
|
||||
group.UserGroup2AppDtos.Select(x => x.AppAlias).ToArray(),
|
||||
group.DefaultPermissions == null ? Enumerable.Empty<string>() : group.DefaultPermissions.ToCharArray().Select(x => x.ToString()));
|
||||
}
|
||||
|
||||
private static bool IsSystemUserGroup(this string groupAlias)
|
||||
{
|
||||
return groupAlias == Constants.Security.AdminGroupAlias
|
||||
|| groupAlias == Constants.Security.SensitiveDataGroupAlias
|
||||
|| groupAlias == Constants.Security.TranslatorGroupAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,16 @@
|
||||
/// <remarks>Is used by <see cref="PublishedContentType"/> constructor to create special property types.</remarks>
|
||||
IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a core (non-user) published property type.
|
||||
/// </summary>
|
||||
/// <param name="contentType">The published content type owning the property.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <param name="dataTypeId">The datatype identifier.</param>
|
||||
/// <param name="variations">The variations.</param>
|
||||
/// <remarks>Is used by <see cref="PublishedContentType"/> constructor to create special property types.</remarks>
|
||||
IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a published datatype.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
@@ -76,7 +77,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
return type;
|
||||
var def = type.GetGenericTypeDefinition();
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
throw new PanicException($"The type {type} has not generic type definition");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
|
||||
return def.MakeGenericType(args);
|
||||
@@ -115,7 +116,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
return type.FullName;
|
||||
var def = type.GetGenericTypeDefinition();
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
throw new PanicException($"The type {type} has not generic type definition");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
|
||||
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
|
||||
|
||||
@@ -92,26 +92,26 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
var aliases = new HashSet<string>(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach ((var alias, (var dataTypeId, var editorAlias)) in BuiltinMemberProperties)
|
||||
foreach (var (alias, dataTypeId) in BuiltinMemberProperties)
|
||||
{
|
||||
if (aliases.Contains(alias)) continue;
|
||||
propertyTypes.Add(factory.CreatePropertyType(this, alias, dataTypeId, ContentVariation.Nothing));
|
||||
propertyTypes.Add(factory.CreateCorePropertyType(this, alias, dataTypeId, ContentVariation.Nothing));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate!
|
||||
private static readonly Dictionary<string, (int, string)> BuiltinMemberProperties = new Dictionary<string, (int, string)>
|
||||
private static readonly Dictionary<string, int> BuiltinMemberProperties = new Dictionary<string, int>
|
||||
{
|
||||
{ "Email", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
|
||||
{ "Username", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
|
||||
{ "PasswordQuestion", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
|
||||
{ "Comments", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
|
||||
{ "IsApproved", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) },
|
||||
{ "IsLockedOut", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) },
|
||||
{ "LastLockoutDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
|
||||
{ "CreateDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
|
||||
{ "LastLoginDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
|
||||
{ "LastPasswordChangeDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
|
||||
{ "Email", Constants.DataTypes.Textbox },
|
||||
{ "Username", Constants.DataTypes.Textbox },
|
||||
{ "PasswordQuestion", Constants.DataTypes.Textbox },
|
||||
{ "Comments", Constants.DataTypes.Textbox },
|
||||
{ "IsApproved", Constants.DataTypes.Boolean },
|
||||
{ "IsLockedOut", Constants.DataTypes.Boolean },
|
||||
{ "LastLockoutDate", Constants.DataTypes.DateTime },
|
||||
{ "CreateDate", Constants.DataTypes.DateTime },
|
||||
{ "LastLoginDate", Constants.DataTypes.DateTime },
|
||||
{ "LastPasswordChangeDate", Constants.DataTypes.DateTime },
|
||||
};
|
||||
|
||||
#region Content type
|
||||
|
||||
@@ -61,6 +61,12 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing)
|
||||
{
|
||||
return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is for tests and is not intended to be used directly from application code.
|
||||
/// </summary>
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
if (user.Avatar.IsNullOrWhiteSpace())
|
||||
{
|
||||
var gravatarHash = user.Email.ToMd5();
|
||||
var gravatarHash = user.Email.GenerateHash<MD5>();
|
||||
var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404";
|
||||
|
||||
//try Gravatar
|
||||
|
||||
@@ -542,7 +542,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
return "\"{0}\"".InvariantFormat(obj);
|
||||
}
|
||||
if (obj is int || obj is Int16 || obj is Int64 || obj is float || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is float? || obj is double? || obj is bool?)
|
||||
if (obj is int || obj is short || obj is long || obj is float || obj is double || obj is bool || obj is int? || obj is float? || obj is double? || obj is bool?)
|
||||
{
|
||||
return "{0}".InvariantFormat(obj);
|
||||
}
|
||||
@@ -723,7 +723,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
return typeConverter;
|
||||
}
|
||||
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(target);
|
||||
if (converter.CanConvertFrom(source))
|
||||
{
|
||||
@@ -788,6 +788,6 @@ namespace Umbraco.Core
|
||||
return BoolConvertCache[type] = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
@@ -12,7 +11,9 @@ using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Packaging;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
@@ -26,13 +27,14 @@ namespace Umbraco.Core.Packaging
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IContentService _contentService;
|
||||
|
||||
public PackageDataInstallation(ILogger logger, IFileService fileService, IMacroService macroService, ILocalizationService localizationService,
|
||||
IDataTypeService dataTypeService, IEntityService entityService, IContentTypeService contentTypeService,
|
||||
IContentService contentService, PropertyEditorCollection propertyEditors)
|
||||
IContentService contentService, PropertyEditorCollection propertyEditors, IScopeProvider scopeProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileService = fileService;
|
||||
@@ -40,12 +42,13 @@ namespace Umbraco.Core.Packaging
|
||||
_localizationService = localizationService;
|
||||
_dataTypeService = dataTypeService;
|
||||
_propertyEditors = propertyEditors;
|
||||
_scopeProvider = scopeProvider;
|
||||
_entityService = entityService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_contentService = contentService;
|
||||
}
|
||||
|
||||
#region Uninstall
|
||||
#region Install/Uninstall
|
||||
|
||||
public UninstallationSummary UninstallPackageData(PackageDefinition package, int userId)
|
||||
{
|
||||
@@ -58,93 +61,97 @@ namespace Umbraco.Core.Packaging
|
||||
var removedDataTypes = new List<IDataType>();
|
||||
var removedLanguages = new List<ILanguage>();
|
||||
|
||||
|
||||
//Uninstall templates
|
||||
foreach (var item in package.Templates.ToArray())
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var found = _fileService.GetTemplate(nId);
|
||||
if (found != null)
|
||||
//Uninstall templates
|
||||
foreach (var item in package.Templates.ToArray())
|
||||
{
|
||||
removedTemplates.Add(found);
|
||||
_fileService.DeleteTemplate(found.Alias, userId);
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var found = _fileService.GetTemplate(nId);
|
||||
if (found != null)
|
||||
{
|
||||
removedTemplates.Add(found);
|
||||
_fileService.DeleteTemplate(found.Alias, userId);
|
||||
}
|
||||
package.Templates.Remove(nId.ToString());
|
||||
}
|
||||
package.Templates.Remove(nId.ToString());
|
||||
}
|
||||
|
||||
//Uninstall macros
|
||||
foreach (var item in package.Macros.ToArray())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var macro = _macroService.GetById(nId);
|
||||
if (macro != null)
|
||||
//Uninstall macros
|
||||
foreach (var item in package.Macros.ToArray())
|
||||
{
|
||||
removedMacros.Add(macro);
|
||||
_macroService.Delete(macro, userId);
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var macro = _macroService.GetById(nId);
|
||||
if (macro != null)
|
||||
{
|
||||
removedMacros.Add(macro);
|
||||
_macroService.Delete(macro, userId);
|
||||
}
|
||||
package.Macros.Remove(nId.ToString());
|
||||
}
|
||||
package.Macros.Remove(nId.ToString());
|
||||
}
|
||||
|
||||
//Remove Document Types
|
||||
var contentTypes = new List<IContentType>();
|
||||
var contentTypeService = _contentTypeService;
|
||||
foreach (var item in package.DocumentTypes.ToArray())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var contentType = contentTypeService.Get(nId);
|
||||
if (contentType == null) continue;
|
||||
contentTypes.Add(contentType);
|
||||
package.DocumentTypes.Remove(nId.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
//Order the DocumentTypes before removing them
|
||||
if (contentTypes.Any())
|
||||
{
|
||||
// TODO: I don't think this ordering is necessary
|
||||
var orderedTypes = (from contentType in contentTypes
|
||||
orderby contentType.ParentId descending, contentType.Id descending
|
||||
select contentType).ToList();
|
||||
removedContentTypes.AddRange(orderedTypes);
|
||||
contentTypeService.Delete(orderedTypes, userId);
|
||||
}
|
||||
|
||||
//Remove Dictionary items
|
||||
foreach (var item in package.DictionaryItems.ToArray())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var di = _localizationService.GetDictionaryItemById(nId);
|
||||
if (di != null)
|
||||
//Remove Document Types
|
||||
var contentTypes = new List<IContentType>();
|
||||
var contentTypeService = _contentTypeService;
|
||||
foreach (var item in package.DocumentTypes.ToArray())
|
||||
{
|
||||
removedDictionaryItems.Add(di);
|
||||
_localizationService.Delete(di, userId);
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var contentType = contentTypeService.Get(nId);
|
||||
if (contentType == null) continue;
|
||||
contentTypes.Add(contentType);
|
||||
package.DocumentTypes.Remove(nId.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
package.DictionaryItems.Remove(nId.ToString());
|
||||
}
|
||||
|
||||
//Remove Data types
|
||||
foreach (var item in package.DataTypes.ToArray())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var dtd = _dataTypeService.GetDataType(nId);
|
||||
if (dtd != null)
|
||||
//Order the DocumentTypes before removing them
|
||||
if (contentTypes.Any())
|
||||
{
|
||||
removedDataTypes.Add(dtd);
|
||||
_dataTypeService.Delete(dtd, userId);
|
||||
// TODO: I don't think this ordering is necessary
|
||||
var orderedTypes = (from contentType in contentTypes
|
||||
orderby contentType.ParentId descending, contentType.Id descending
|
||||
select contentType).ToList();
|
||||
removedContentTypes.AddRange(orderedTypes);
|
||||
contentTypeService.Delete(orderedTypes, userId);
|
||||
}
|
||||
package.DataTypes.Remove(nId.ToString());
|
||||
}
|
||||
|
||||
//Remove Langs
|
||||
foreach (var item in package.Languages.ToArray())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var lang = _localizationService.GetLanguageById(nId);
|
||||
if (lang != null)
|
||||
//Remove Dictionary items
|
||||
foreach (var item in package.DictionaryItems.ToArray())
|
||||
{
|
||||
removedLanguages.Add(lang);
|
||||
_localizationService.Delete(lang, userId);
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var di = _localizationService.GetDictionaryItemById(nId);
|
||||
if (di != null)
|
||||
{
|
||||
removedDictionaryItems.Add(di);
|
||||
_localizationService.Delete(di, userId);
|
||||
}
|
||||
package.DictionaryItems.Remove(nId.ToString());
|
||||
}
|
||||
package.Languages.Remove(nId.ToString());
|
||||
|
||||
//Remove Data types
|
||||
foreach (var item in package.DataTypes.ToArray())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var dtd = _dataTypeService.GetDataType(nId);
|
||||
if (dtd != null)
|
||||
{
|
||||
removedDataTypes.Add(dtd);
|
||||
_dataTypeService.Delete(dtd, userId);
|
||||
}
|
||||
package.DataTypes.Remove(nId.ToString());
|
||||
}
|
||||
|
||||
//Remove Langs
|
||||
foreach (var item in package.Languages.ToArray())
|
||||
{
|
||||
if (int.TryParse(item, out var nId) == false) continue;
|
||||
var lang = _localizationService.GetLanguageById(nId);
|
||||
if (lang != null)
|
||||
{
|
||||
removedLanguages.Add(lang);
|
||||
_localizationService.Delete(lang, userId);
|
||||
}
|
||||
package.Languages.Remove(nId.ToString());
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
// create a summary of what was actually removed, for PackagingService.UninstalledPackage
|
||||
@@ -165,14 +172,40 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
}
|
||||
|
||||
public InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId)
|
||||
{
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
var installationSummary = new InstallationSummary
|
||||
{
|
||||
DataTypesInstalled = ImportDataTypes(compiledPackage.DataTypes.ToList(), userId),
|
||||
LanguagesInstalled = ImportLanguages(compiledPackage.Languages, userId),
|
||||
DictionaryItemsInstalled = ImportDictionaryItems(compiledPackage.DictionaryItems, userId),
|
||||
MacrosInstalled = ImportMacros(compiledPackage.Macros, userId),
|
||||
TemplatesInstalled = ImportTemplates(compiledPackage.Templates.ToList(), userId),
|
||||
DocumentTypesInstalled = ImportDocumentTypes(compiledPackage.DocumentTypes, userId)
|
||||
};
|
||||
|
||||
//we need a reference to the imported doc types to continue
|
||||
var importedDocTypes = installationSummary.DocumentTypesInstalled.ToDictionary(x => x.Alias, x => x);
|
||||
|
||||
installationSummary.StylesheetsInstalled = ImportStylesheets(compiledPackage.Stylesheets, userId);
|
||||
installationSummary.ContentInstalled = ImportContent(compiledPackage.Documents, importedDocTypes, userId);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return installationSummary;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Content
|
||||
|
||||
|
||||
public IEnumerable<IContent> ImportContent(IEnumerable<CompiledPackageDocument> docs, IDictionary<string, IContentType> importedDocumentTypes, int userId)
|
||||
public IReadOnlyList<IContent> ImportContent(IEnumerable<CompiledPackageDocument> docs, IDictionary<string, IContentType> importedDocumentTypes, int userId)
|
||||
{
|
||||
return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId));
|
||||
return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -353,7 +386,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
#region DocumentTypes
|
||||
|
||||
public IEnumerable<IContentType> ImportDocumentType(XElement docTypeElement, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentType(XElement docTypeElement, int userId)
|
||||
{
|
||||
return ImportDocumentTypes(new[] { docTypeElement }, userId);
|
||||
}
|
||||
@@ -364,7 +397,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="docTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
public IEnumerable<IContentType> ImportDocumentTypes(IEnumerable<XElement> docTypeElements, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentTypes(IEnumerable<XElement> docTypeElements, int userId)
|
||||
{
|
||||
return ImportDocumentTypes(docTypeElements.ToList(), true, userId);
|
||||
}
|
||||
@@ -376,7 +409,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="importStructure">Boolean indicating whether or not to import the </param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
public IEnumerable<IContentType> ImportDocumentTypes(IReadOnlyCollection<XElement> unsortedDocumentTypes, bool importStructure, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentTypes(IReadOnlyCollection<XElement> unsortedDocumentTypes, bool importStructure, int userId)
|
||||
{
|
||||
var importedContentTypes = new Dictionary<string, IContentType>();
|
||||
|
||||
@@ -575,12 +608,11 @@ namespace Umbraco.Core.Packaging
|
||||
contentType.Thumbnail = infoElement.Element("Thumbnail").Value;
|
||||
contentType.Description = infoElement.Element("Description").Value;
|
||||
|
||||
//NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it.
|
||||
//NOTE AllowAtRoot, IsListView, IsElement and Variations are new properties in the package xml so we need to verify it exists before using it.
|
||||
var allowAtRoot = infoElement.Element("AllowAtRoot");
|
||||
if (allowAtRoot != null)
|
||||
contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true");
|
||||
|
||||
//NOTE IsListView is a new property in the package xml so we need to verify it exists before using it.
|
||||
var isListView = infoElement.Element("IsListView");
|
||||
if (isListView != null)
|
||||
contentType.IsContainer = isListView.Value.InvariantEquals("true");
|
||||
@@ -589,6 +621,10 @@ namespace Umbraco.Core.Packaging
|
||||
if (isElement != null)
|
||||
contentType.IsElement = isElement.Value.InvariantEquals("true");
|
||||
|
||||
var variationsElement = infoElement.Element("Variations");
|
||||
if (variationsElement != null)
|
||||
contentType.Variations = (ContentVariation)Enum.Parse(typeof(ContentVariation), variationsElement.Value);
|
||||
|
||||
//Name of the master corresponds to the parent and we need to ensure that the Parent Id is set
|
||||
var masterElement = infoElement.Element("Master");
|
||||
if (masterElement != null)
|
||||
@@ -614,7 +650,7 @@ namespace Umbraco.Core.Packaging
|
||||
var compositionContentType = importedContentTypes.ContainsKey(compositionAlias)
|
||||
? importedContentTypes[compositionAlias]
|
||||
: _contentTypeService.Get(compositionAlias);
|
||||
var added = contentType.AddContentType(compositionContentType);
|
||||
contentType.AddContentType(compositionContentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,9 +784,14 @@ namespace Umbraco.Core.Packaging
|
||||
{
|
||||
Name = property.Element("Name").Value,
|
||||
Description = (string)property.Element("Description"),
|
||||
Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false,
|
||||
Mandatory = property.Element("Mandatory") != null
|
||||
? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true")
|
||||
: false,
|
||||
ValidationRegExp = (string)property.Element("Validation"),
|
||||
SortOrder = sortOrder
|
||||
SortOrder = sortOrder,
|
||||
Variations = property.Element("Variations") != null
|
||||
? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value)
|
||||
: ContentVariation.Nothing
|
||||
};
|
||||
|
||||
var tab = (string)property.Element("Tab");
|
||||
@@ -817,7 +858,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="dataTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the user</param>
|
||||
/// <returns>An enumerable list of generated DataTypeDefinitions</returns>
|
||||
public IEnumerable<IDataType> ImportDataTypes(IReadOnlyCollection<XElement> dataTypeElements, int userId)
|
||||
public IReadOnlyList<IDataType> ImportDataTypes(IReadOnlyCollection<XElement> dataTypeElements, int userId)
|
||||
{
|
||||
var dataTypes = new List<IDataType>();
|
||||
|
||||
@@ -946,13 +987,13 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="dictionaryItemElementList">Xml to import</param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns>An enumerable list of dictionary items</returns>
|
||||
public IEnumerable<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, int userId)
|
||||
public IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, int userId)
|
||||
{
|
||||
var languages = _localizationService.GetAllLanguages().ToList();
|
||||
return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId);
|
||||
}
|
||||
|
||||
private IEnumerable<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, List<ILanguage> languages, Guid? parentId, int userId)
|
||||
private IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, List<ILanguage> languages, Guid? parentId, int userId)
|
||||
{
|
||||
var items = new List<IDictionaryItem>();
|
||||
foreach (var dictionaryItemElement in dictionaryItemElementList)
|
||||
@@ -1029,7 +1070,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="languageElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation</param>
|
||||
/// <returns>An enumerable list of generated languages</returns>
|
||||
public IEnumerable<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId)
|
||||
public IReadOnlyList<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId)
|
||||
{
|
||||
var list = new List<ILanguage>();
|
||||
foreach (var languageElement in languageElements)
|
||||
@@ -1058,7 +1099,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="macroElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IMacro> ImportMacros(IEnumerable<XElement> macroElements, int userId)
|
||||
public IReadOnlyList<IMacro> ImportMacros(IEnumerable<XElement> macroElements, int userId)
|
||||
{
|
||||
var macros = macroElements.Select(ParseMacroElement).ToList();
|
||||
|
||||
@@ -1148,7 +1189,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
#region Stylesheets
|
||||
|
||||
public IEnumerable<IFile> ImportStylesheets(IEnumerable<XElement> stylesheetElements, int userId)
|
||||
public IReadOnlyList<IFile> ImportStylesheets(IEnumerable<XElement> stylesheetElements, int userId)
|
||||
{
|
||||
var result = new List<IFile>();
|
||||
|
||||
@@ -1216,7 +1257,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="templateElements">Xml to import</param>
|
||||
/// <param name="userId">Optional user id</param>
|
||||
/// <returns>An enumerable list of generated Templates</returns>
|
||||
public IEnumerable<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
public IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
{
|
||||
var templates = new List<ITemplate>();
|
||||
|
||||
|
||||
@@ -90,21 +90,8 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
public InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId)
|
||||
{
|
||||
var installationSummary = new InstallationSummary
|
||||
{
|
||||
DataTypesInstalled = _packageDataInstallation.ImportDataTypes(compiledPackage.DataTypes.ToList(), userId),
|
||||
LanguagesInstalled = _packageDataInstallation.ImportLanguages(compiledPackage.Languages, userId),
|
||||
DictionaryItemsInstalled = _packageDataInstallation.ImportDictionaryItems(compiledPackage.DictionaryItems, userId),
|
||||
MacrosInstalled = _packageDataInstallation.ImportMacros(compiledPackage.Macros, userId),
|
||||
TemplatesInstalled = _packageDataInstallation.ImportTemplates(compiledPackage.Templates.ToList(), userId),
|
||||
DocumentTypesInstalled = _packageDataInstallation.ImportDocumentTypes(compiledPackage.DocumentTypes, userId)
|
||||
};
|
||||
var installationSummary = _packageDataInstallation.InstallPackageData(compiledPackage, userId);
|
||||
|
||||
//we need a reference to the imported doc types to continue
|
||||
var importedDocTypes = installationSummary.DocumentTypesInstalled.ToDictionary(x => x.Alias, x => x);
|
||||
|
||||
installationSummary.StylesheetsInstalled = _packageDataInstallation.ImportStylesheets(compiledPackage.Stylesheets, userId);
|
||||
installationSummary.ContentInstalled = _packageDataInstallation.ImportContent(compiledPackage.Documents, importedDocTypes, userId);
|
||||
installationSummary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(compiledPackage.Actions), compiledPackage.Name);
|
||||
installationSummary.MetaData = compiledPackage;
|
||||
installationSummary.FilesInstalled = packageDefinition.Files;
|
||||
|
||||
@@ -470,7 +470,7 @@ namespace Umbraco.Core.Persistence
|
||||
break;
|
||||
|
||||
case SqlDbType.SmallInt:
|
||||
dataType = typeof(Int16);
|
||||
dataType = typeof(short);
|
||||
dataTypeName = "smallint";
|
||||
break;
|
||||
|
||||
@@ -688,34 +688,34 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
DataColumnCollection columns = _schemaTable.Columns;
|
||||
|
||||
columns.Add(SchemaTableColumn.ColumnName, typeof(System.String));
|
||||
columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(System.Int32));
|
||||
columns.Add(SchemaTableColumn.ColumnSize, typeof(System.Int32));
|
||||
columns.Add(SchemaTableColumn.NumericPrecision, typeof(System.Int16));
|
||||
columns.Add(SchemaTableColumn.NumericScale, typeof(System.Int16));
|
||||
columns.Add(SchemaTableColumn.IsUnique, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableColumn.IsKey, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(System.String));
|
||||
columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(System.String));
|
||||
columns.Add(SchemaTableColumn.BaseColumnName, typeof(System.String));
|
||||
columns.Add(SchemaTableColumn.BaseSchemaName, typeof(System.String));
|
||||
columns.Add(SchemaTableColumn.BaseTableName, typeof(System.String));
|
||||
columns.Add(SchemaTableColumn.DataType, typeof(System.Type));
|
||||
columns.Add(SchemaTableColumn.AllowDBNull, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableColumn.ProviderType, typeof(System.Int32));
|
||||
columns.Add(SchemaTableColumn.IsAliased, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableColumn.IsExpression, typeof(System.Boolean));
|
||||
columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableColumn.IsLong, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(System.Boolean));
|
||||
columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(System.Type));
|
||||
columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(System.String));
|
||||
columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(System.String));
|
||||
columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(System.String));
|
||||
columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(System.String));
|
||||
columns.Add(SchemaTableColumn.ColumnName, typeof(string));
|
||||
columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
|
||||
columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
|
||||
columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
|
||||
columns.Add(SchemaTableColumn.NumericScale, typeof(short));
|
||||
columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
|
||||
columns.Add(SchemaTableColumn.IsKey, typeof(bool));
|
||||
columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
|
||||
columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
|
||||
columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
|
||||
columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
|
||||
columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
|
||||
columns.Add(SchemaTableColumn.DataType, typeof(Type));
|
||||
columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
|
||||
columns.Add(SchemaTableColumn.ProviderType, typeof(int));
|
||||
columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
|
||||
columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
|
||||
columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(bool));
|
||||
columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
|
||||
columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
|
||||
columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool));
|
||||
columns.Add(SchemaTableColumn.IsLong, typeof(bool));
|
||||
columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
|
||||
columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
|
||||
columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(string));
|
||||
columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(string));
|
||||
columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(string));
|
||||
columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(string));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1090,7 +1090,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// <seealso cref="IDataRecord.GetDecimal(Int32)"/>
|
||||
public decimal GetDecimal(int i)
|
||||
{
|
||||
return (Decimal)GetValue(i);
|
||||
return (decimal)GetValue(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
internal static class DatabaseNodeLockExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ValidateDatabase(IUmbracoDatabase database)
|
||||
{
|
||||
if (database == null)
|
||||
throw new ArgumentNullException("database");
|
||||
if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
}
|
||||
|
||||
// updating a record within a repeatable-read transaction gets an exclusive lock on
|
||||
// that record which will be kept until the transaction is ended, effectively locking
|
||||
// out all other accesses to that record - thus obtaining an exclusive lock over the
|
||||
// protected resources.
|
||||
public static void AcquireLockNodeWriteLock(this IUmbracoDatabase database, int nodeId)
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
|
||||
// reading a record within a repeatable-read transaction gets a shared lock on
|
||||
// that record which will be kept until the transaction is ended, effectively preventing
|
||||
// other write accesses to that record - thus obtaining a shared lock over the protected
|
||||
// resources.
|
||||
public static void AcquireLockNodeReadLock(this IUmbracoDatabase database, int nodeId)
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
// TODO: Would be good to use this exception type anytime we cannot find an entity
|
||||
|
||||
/// <summary>
|
||||
/// An exception used to indicate that an Umbraco entity could not be found.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
public static IEnumerable<Property> BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection<PropertyDataDto> dtos, int publishedVersionId, ILanguageRepository languageRepository)
|
||||
{
|
||||
var properties = new List<Property>();
|
||||
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>) x);
|
||||
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>)x);
|
||||
|
||||
foreach (var propertyType in propertyTypes)
|
||||
{
|
||||
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
/// <param name="properties">The properties to map</param>
|
||||
/// <param name="languageRepository"></param>
|
||||
/// <param name="edited">out parameter indicating that one or more properties have been edited</param>
|
||||
/// <param name="editedCultures">out parameter containing a collection of edited cultures when the contentVariation varies by culture</param>
|
||||
/// <param name="editedCultures">
|
||||
/// Out parameter containing a collection of edited cultures when the contentVariation varies by culture.
|
||||
/// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable<Property> properties,
|
||||
ILanguageRepository languageRepository, out bool edited, out HashSet<string> editedCultures)
|
||||
ILanguageRepository languageRepository, out bool edited,
|
||||
out HashSet<string> editedCultures)
|
||||
{
|
||||
var propertyDataDtos = new List<PropertyDataDto>();
|
||||
edited = false;
|
||||
@@ -130,6 +134,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
// publishing = deal with edit and published values
|
||||
foreach (var propertyValue in property.Values)
|
||||
{
|
||||
var isInvariantValue = propertyValue.Culture == null;
|
||||
var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
|
||||
|
||||
// deal with published value
|
||||
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
|
||||
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
|
||||
@@ -138,26 +145,36 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
if (propertyValue.EditedValue != null)
|
||||
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
|
||||
|
||||
// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
|
||||
// administrator has previously changed the property type to be variant vs invariant.
|
||||
// We need to check for this scenario here because otherwise the editedCultures and edited flags
|
||||
// will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to
|
||||
// only process edited cultures based on the current value type and how the property varies.
|
||||
// The above logic will still persist the currently saved property value for each culture in case the admin
|
||||
// decides to swap the property's variance again, in which case the edited flag will be recalculated.
|
||||
|
||||
if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue)
|
||||
continue;
|
||||
|
||||
// use explicit equals here, else object comparison fails at comparing eg strings
|
||||
var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue);
|
||||
|
||||
edited |= !sameValues;
|
||||
|
||||
if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported
|
||||
&& propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral
|
||||
&& !sameValues) // and edited and published are different
|
||||
if (entityVariesByCulture && !sameValues)
|
||||
{
|
||||
editedCultures.Add(propertyValue.Culture); // report culture as edited
|
||||
}
|
||||
if (isCultureValue)
|
||||
{
|
||||
editedCultures.Add(propertyValue.Culture); // report culture as edited
|
||||
}
|
||||
else if (isInvariantValue)
|
||||
{
|
||||
// flag culture as edited if it contains an edited invariant property
|
||||
if (defaultCulture == null)
|
||||
defaultCulture = languageRepository.GetDefaultIsoCode();
|
||||
|
||||
// flag culture as edited if it contains an edited invariant property
|
||||
if (propertyValue.Culture == null //invariant property
|
||||
&& !sameValues // and edited and published are different
|
||||
&& entityVariesByCulture) //only when the entity is variant
|
||||
{
|
||||
if (defaultCulture == null)
|
||||
defaultCulture = languageRepository.GetDefaultIsoCode();
|
||||
|
||||
editedCultures.Add(defaultCulture);
|
||||
editedCultures.Add(defaultCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +184,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
// not publishing = only deal with edit values
|
||||
if (propertyValue.EditedValue != null)
|
||||
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
|
||||
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
|
||||
}
|
||||
edited = true;
|
||||
}
|
||||
|
||||
@@ -7,5 +7,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public interface IDataTypeRepository : IReadWriteQueryRepository<int, IDataType>
|
||||
{
|
||||
IEnumerable<MoveEventInfo<IDataType>> Move(IDataType toMove, EntityContainer container);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of content type <see cref="Udi"/>s and the property type aliases that use a <see cref="IDataType"/>
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
totalRecords = page.TotalItems;
|
||||
|
||||
var items = page.Items.Select(
|
||||
dto => new AuditItem(dto.Id, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
|
||||
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
|
||||
|
||||
// map the DateStamp
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
|
||||
@@ -512,31 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
foreach (var a in allPropertyDataDtos)
|
||||
a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId];
|
||||
|
||||
// prefetch configuration for tag properties
|
||||
var tagEditors = new Dictionary<string, TagConfiguration>();
|
||||
foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values)
|
||||
{
|
||||
var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias;
|
||||
var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute();
|
||||
if (editorAttribute == null) continue;
|
||||
var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration;
|
||||
var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource)
|
||||
? new TagConfiguration()
|
||||
: JsonConvert.DeserializeObject<TagConfiguration>(tagConfigurationSource);
|
||||
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter;
|
||||
tagEditors[editorAlias] = tagConfiguration;
|
||||
}
|
||||
|
||||
// now we have
|
||||
// - the definitions
|
||||
// - all property data dtos
|
||||
// - tag editors
|
||||
// - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
|
||||
// and we need to build the proper property collections
|
||||
|
||||
return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
|
||||
return GetPropertyCollections(temps, allPropertyDataDtos);
|
||||
}
|
||||
|
||||
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos, Dictionary<string, TagConfiguration> tagConfigurations)
|
||||
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos)
|
||||
where T : class, IContentBase
|
||||
{
|
||||
var result = new Dictionary<int, PropertyCollection>();
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
@@ -90,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
|
||||
else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
|
||||
contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
|
||||
else throw new Exception("panic");
|
||||
else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
|
||||
contentTypes.Add(contentType.Id, contentType);
|
||||
|
||||
// map allowed content types
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>, IContentTypeRepository
|
||||
{
|
||||
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
|
||||
@@ -56,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// the cache policy will always want everything
|
||||
// even GetMany(ids) gets everything and filters afterwards
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
if (ids.Any()) throw new PanicException("There can be no ids specified");
|
||||
return CommonRepository.GetAllTypes().OfType<IContentType>();
|
||||
}
|
||||
|
||||
|
||||
+344
-128
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
@@ -15,6 +16,7 @@ using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
@@ -26,14 +28,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
internal abstract class ContentTypeRepositoryBase<TEntity> : NPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
|
||||
where TEntity : class, IContentTypeComposition
|
||||
{
|
||||
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger)
|
||||
{
|
||||
CommonRepository = commonRepository;
|
||||
LanguageRepository = languageRepository;
|
||||
}
|
||||
|
||||
protected IContentTypeCommonRepository CommonRepository { get; }
|
||||
|
||||
protected ILanguageRepository LanguageRepository { get; }
|
||||
protected abstract bool SupportsPublishing { get; }
|
||||
|
||||
public IEnumerable<MoveEventInfo<TEntity>> Move(TEntity moving, EntityContainer container)
|
||||
@@ -98,6 +101,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected void PersistNewBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
ValidateVariations(entity);
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
//Cannot add a duplicate content type
|
||||
@@ -163,11 +168,11 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
foreach (var allowedContentType in entity.AllowedContentTypes)
|
||||
{
|
||||
Database.Insert(new ContentTypeAllowedContentTypeDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
AllowedId = allowedContentType.Id.Value,
|
||||
SortOrder = allowedContentType.SortOrder
|
||||
});
|
||||
{
|
||||
Id = entity.Id,
|
||||
AllowedId = allowedContentType.Id.Value,
|
||||
SortOrder = allowedContentType.SortOrder
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +219,9 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
|
||||
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
CorrectPropertyTypeVariations(entity);
|
||||
ValidateVariations(entity);
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
// ensure the alias is not used already
|
||||
@@ -370,7 +378,7 @@ AND umbracoNode.id <> @id",
|
||||
foreach (var propertyGroup in entity.PropertyGroups)
|
||||
{
|
||||
// insert or update group
|
||||
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id);
|
||||
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id);
|
||||
var groupId = propertyGroup.HasIdentity
|
||||
? Database.Update(groupDto)
|
||||
: Convert.ToInt32(Database.Insert(groupDto));
|
||||
@@ -388,7 +396,7 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
//check if the content type variation has been changed
|
||||
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
|
||||
var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
|
||||
var oldContentTypeVariation = (ContentVariation)dtoPk.Variations;
|
||||
var newContentTypeVariation = entity.Variations;
|
||||
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
|
||||
if (contentTypeVariationChanging)
|
||||
@@ -404,26 +412,7 @@ AND umbracoNode.id <> @id",
|
||||
// note: this only deals with *local* property types, we're dealing w/compositions later below
|
||||
foreach (var propertyType in entity.PropertyTypes)
|
||||
{
|
||||
if (contentTypeVariationChanging)
|
||||
{
|
||||
// content type is changing
|
||||
switch (newContentTypeVariation)
|
||||
{
|
||||
case ContentVariation.Nothing: // changing to Nothing
|
||||
// all property types must change to Nothing
|
||||
propertyType.Variations = ContentVariation.Nothing;
|
||||
break;
|
||||
case ContentVariation.Culture: // changing to Culture
|
||||
// all property types can remain Nothing
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); // TODO: Support this
|
||||
}
|
||||
}
|
||||
|
||||
// then, track each property individually
|
||||
// track each property individually
|
||||
if (propertyType.IsPropertyDirty("Variations"))
|
||||
{
|
||||
// allocate the list only when needed
|
||||
@@ -449,23 +438,19 @@ AND umbracoNode.id <> @id",
|
||||
// via composition, with their original variations (ie not filtered by this
|
||||
// content type variations - we need this true value to make decisions.
|
||||
|
||||
foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes)
|
||||
propertyTypeVariationChanges = propertyTypeVariationChanges ?? new Dictionary<int, (ContentVariation, ContentVariation)>();
|
||||
|
||||
foreach (var composedPropertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
|
||||
{
|
||||
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
|
||||
throw new NotSupportedException(); // TODO: support this
|
||||
if (composedPropertyType.Variations == ContentVariation.Nothing) continue;
|
||||
|
||||
if (propertyType.Variations == ContentVariation.Culture)
|
||||
{
|
||||
if (propertyTypeVariationChanges == null)
|
||||
propertyTypeVariationChanges = new Dictionary<int, (ContentVariation, ContentVariation)>();
|
||||
// Determine target variation of the composed property type.
|
||||
// The composed property is only considered culture variant when the base content type is also culture variant.
|
||||
// The composed property is only considered segment variant when the base content type is also segment variant.
|
||||
// Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
|
||||
var target = newContentTypeVariation & composedPropertyType.Variations;
|
||||
|
||||
// if content type moves to Culture, property type becomes Culture here again
|
||||
// if content type moves to Nothing, property type becomes Nothing here
|
||||
if (newContentTypeVariation == ContentVariation.Culture)
|
||||
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Nothing, ContentVariation.Culture);
|
||||
else if (newContentTypeVariation == ContentVariation.Nothing)
|
||||
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Culture, ContentVariation.Nothing);
|
||||
}
|
||||
propertyTypeVariationChanges[composedPropertyType.Id] = (composedPropertyType.Variations, target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +491,7 @@ AND umbracoNode.id <> @id",
|
||||
var impacted = GetImpactedContentTypes(entity, all);
|
||||
|
||||
// if some property types have actually changed, move their variant data
|
||||
if (propertyTypeVariationChanges != null)
|
||||
if (propertyTypeVariationChanges?.Count > 0)
|
||||
MovePropertyTypeVariantData(propertyTypeVariationChanges, impacted);
|
||||
|
||||
// deal with orphan properties: those that were in a deleted tab,
|
||||
@@ -518,6 +503,42 @@ AND umbracoNode.id <> @id",
|
||||
CommonRepository.ClearCache(); // always
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Corrects the property type variations for the given entity
|
||||
/// to make sure the property type variation is compatible with the
|
||||
/// variation set on the entity itself.
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity to correct properties for</param>
|
||||
private void CorrectPropertyTypeVariations(IContentTypeComposition entity)
|
||||
{
|
||||
// Update property variations based on the content type variation
|
||||
foreach (var propertyType in entity.PropertyTypes)
|
||||
{
|
||||
// Determine variation for the property type.
|
||||
// The property is only considered culture variant when the base content type is also culture variant.
|
||||
// The property is only considered segment variant when the base content type is also segment variant.
|
||||
// Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
|
||||
propertyType.Variations = entity.Variations & propertyType.Variations;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity for which the property types will be validated</param>
|
||||
private void ValidateVariations(IContentTypeComposition entity)
|
||||
{
|
||||
foreach (var prop in entity.PropertyTypes)
|
||||
{
|
||||
// The variation of a property is only allowed if all its variation flags
|
||||
// are also set on the entity itself. It cannot set anything that is not also set by the content type.
|
||||
// For example, when entity.Variations is set to Culture a property cannot be set to Segment.
|
||||
var isValid = entity.Variations.HasFlag(prop.Variations);
|
||||
if (!isValid)
|
||||
throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
|
||||
{
|
||||
var impact = new List<IContentTypeComposition>();
|
||||
@@ -525,12 +546,12 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
var tree = new Dictionary<int, List<IContentTypeComposition>>();
|
||||
foreach (var x in all)
|
||||
foreach (var y in x.ContentTypeComposition)
|
||||
{
|
||||
if (!tree.TryGetValue(y.Id, out var list))
|
||||
list = tree[y.Id] = new List<IContentTypeComposition>();
|
||||
list.Add(x);
|
||||
}
|
||||
foreach (var y in x.ContentTypeComposition)
|
||||
{
|
||||
if (!tree.TryGetValue(y.Id, out var list))
|
||||
list = tree[y.Id] = new List<IContentTypeComposition>();
|
||||
list.Add(x);
|
||||
}
|
||||
|
||||
var nset = new List<IContentTypeComposition>();
|
||||
do
|
||||
@@ -572,7 +593,7 @@ AND umbracoNode.id <> @id",
|
||||
// new property type, ignore
|
||||
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
|
||||
continue;
|
||||
var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
|
||||
var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly
|
||||
|
||||
// only those property types that *actually* changed
|
||||
var newVariation = propertyType.Variations;
|
||||
@@ -636,25 +657,27 @@ AND umbracoNode.id <> @id",
|
||||
var impactedL = impacted.Select(x => x.Id).ToList();
|
||||
|
||||
//Group by the "To" variation so we can bulk update in the correct batches
|
||||
foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
|
||||
foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value))
|
||||
{
|
||||
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
|
||||
var toVariation = grouping.Key;
|
||||
var (FromVariation, ToVariation) = grouping.Key;
|
||||
|
||||
switch (toVariation)
|
||||
var fromCultureEnabled = FromVariation.HasFlag(ContentVariation.Culture);
|
||||
var toCultureEnabled = ToVariation.HasFlag(ContentVariation.Culture);
|
||||
|
||||
if (!fromCultureEnabled && toCultureEnabled)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); // TODO: Support this
|
||||
// Culture has been enabled
|
||||
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
|
||||
}
|
||||
else if (fromCultureEnabled && !toCultureEnabled)
|
||||
{
|
||||
// Culture has been disabled
|
||||
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,78 +689,72 @@ AND umbracoNode.id <> @id",
|
||||
{
|
||||
var defaultLanguageId = GetDefaultLanguageId();
|
||||
|
||||
switch (toVariation)
|
||||
var cultureIsNotEnabled = !fromVariation.HasFlag(ContentVariation.Culture);
|
||||
var cultureWillBeEnabled = toVariation.HasFlag(ContentVariation.Culture);
|
||||
|
||||
if (cultureIsNotEnabled && cultureWillBeEnabled)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
//move the names
|
||||
//first clear out any existing names that might already exists under the default lang
|
||||
//there's 2x tables to update
|
||||
|
||||
//move the names
|
||||
//first clear out any existing names that might already exists under the default lang
|
||||
//there's 2x tables to update
|
||||
//clear out the versionCultureVariation table
|
||||
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
|
||||
.From<ContentVersionCultureVariationDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
|
||||
var sqlDelete = Sql()
|
||||
.Delete<ContentVersionCultureVariationDto>()
|
||||
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
|
||||
//clear out the versionCultureVariation table
|
||||
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
|
||||
.From<ContentVersionCultureVariationDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
|
||||
var sqlDelete = Sql()
|
||||
.Delete<ContentVersionCultureVariationDto>()
|
||||
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
//clear out the documentCultureVariation table
|
||||
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
|
||||
.From<DocumentCultureVariationDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
|
||||
sqlDelete = Sql()
|
||||
.Delete<DocumentCultureVariationDto>()
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
|
||||
//clear out the documentCultureVariation table
|
||||
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
|
||||
.From<DocumentCultureVariationDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
|
||||
sqlDelete = Sql()
|
||||
.Delete<DocumentCultureVariationDto>()
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
//now we need to insert names into these 2 tables based on the invariant data
|
||||
|
||||
//now we need to insert names into these 2 tables based on the invariant data
|
||||
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
|
||||
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
|
||||
.Append($", {defaultLanguageId}") //default language ID
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
|
||||
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
|
||||
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
|
||||
.Append($", {defaultLanguageId}") //default language ID
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
Database.Execute(sqlInsert);
|
||||
|
||||
Database.Execute(sqlInsert);
|
||||
//insert rows into the documentCultureVariation table
|
||||
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
|
||||
.AndSelect<NodeDto>(x => x.Text)
|
||||
.Append($", 1, {defaultLanguageId}") //make Available + default language ID
|
||||
.From<DocumentDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
|
||||
//insert rows into the documentCultureVariation table
|
||||
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
|
||||
.AndSelect<NodeDto>(x => x.Text)
|
||||
.Append($", 1, {defaultLanguageId}") //make Available + default language ID
|
||||
.From<DocumentDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
Database.Execute(sqlInsert);
|
||||
}
|
||||
else
|
||||
{
|
||||
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
|
||||
|
||||
Database.Execute(sqlInsert);
|
||||
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
|
||||
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
|
||||
|
||||
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
|
||||
// if we want these SQL statements back, look into GIT history
|
||||
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); // TODO: Support this
|
||||
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
|
||||
// if we want these SQL statements back, look into GIT history
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,6 +980,205 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeIds"></param>
|
||||
/// <param name="contentTypeIds"></param>
|
||||
/// <remarks>
|
||||
/// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false
|
||||
/// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each
|
||||
/// property, culture and current/published version.
|
||||
/// </remarks>
|
||||
private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
|
||||
{
|
||||
var defaultLang = LanguageRepository.GetDefaultId();
|
||||
|
||||
//This will build up a query to get the property values of both the current and the published version so that we can check
|
||||
//based on the current variance of each item to see if it's 'edited' value should be true/false.
|
||||
|
||||
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
|
||||
if (whereInArgsCount > 2000)
|
||||
throw new NotSupportedException("Too many property/content types.");
|
||||
|
||||
var propertySql = Sql()
|
||||
.Select<PropertyDataDto>()
|
||||
.AndSelect<ContentVersionDto>(x => x.NodeId, x => x.Current)
|
||||
.AndSelect<DocumentVersionDto>(x => x.Published)
|
||||
.AndSelect<PropertyTypeDto>(x => x.Variations)
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, PropertyDataDto>((left, right) => left.Id == right.VersionId)
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((left, right) => left.Id == right.PropertyTypeId);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
{
|
||||
propertySql.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>((c, cversion) => c.NodeId == cversion.NodeId);
|
||||
}
|
||||
|
||||
propertySql.LeftJoin<DocumentVersionDto>().On<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Id == docversion.Id)
|
||||
.Where<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Current || docversion.Published)
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
{
|
||||
propertySql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
}
|
||||
|
||||
propertySql
|
||||
.OrderBy<ContentVersionDto>(x => x.NodeId)
|
||||
.OrderBy<PropertyDataDto>(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId);
|
||||
|
||||
//keep track of this node/lang to mark or unmark a culture as edited
|
||||
var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
|
||||
//keep track of which node to mark or unmark as edited
|
||||
var editedDocument = new Dictionary<int, bool>();
|
||||
var nodeId = -1;
|
||||
var propertyTypeId = -1;
|
||||
|
||||
PropertyValueVersionDto pubRow = null;
|
||||
|
||||
//This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data.
|
||||
//Published data will always come before Current data based on the version id sort.
|
||||
//There will only be one published row (max) and one current row per property.
|
||||
foreach (var row in Database.Query<PropertyValueVersionDto>(propertySql))
|
||||
{
|
||||
//make sure to reset on each node/property change
|
||||
if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId)
|
||||
{
|
||||
nodeId = row.NodeId;
|
||||
propertyTypeId = row.PropertyTypeId;
|
||||
pubRow = null;
|
||||
}
|
||||
|
||||
if (row.Published)
|
||||
pubRow = row;
|
||||
|
||||
if (row.Current)
|
||||
{
|
||||
var propVariations = (ContentVariation)row.Variations;
|
||||
|
||||
//if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited
|
||||
if (!propVariations.VariesByCulture() && row.LanguageId.HasValue
|
||||
|| propVariations.VariesByCulture() && !row.LanguageId.HasValue)
|
||||
{
|
||||
//Flag this as not edited for this node/lang if the key doesn't exist
|
||||
if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _))
|
||||
editedLanguageVersions.Add((row.NodeId, row.LanguageId), false);
|
||||
|
||||
//mark as false if the item doesn't exist, else coerce to true
|
||||
editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false;
|
||||
}
|
||||
else if (pubRow == null)
|
||||
{
|
||||
//this would mean that that this property is 'edited' since there is no published version
|
||||
editedLanguageVersions[(row.NodeId, row.LanguageId)] = true;
|
||||
editedDocument[row.NodeId] = true;
|
||||
}
|
||||
//compare the property values, if they differ from versions then flag the current version as edited
|
||||
else if (IsPropertyValueChanged(pubRow, row))
|
||||
{
|
||||
//Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang
|
||||
editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true;
|
||||
editedDocument[row.NodeId] = true;
|
||||
}
|
||||
|
||||
//reset
|
||||
pubRow = null;
|
||||
}
|
||||
}
|
||||
|
||||
//lookup all matching rows in umbracoDocumentCultureVariation
|
||||
var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
|
||||
.SelectMany(_ => Database.Fetch<DocumentCultureVariationDto>(
|
||||
Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
|
||||
//convert to dictionary with the same key type
|
||||
.ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
|
||||
|
||||
var toUpdate = new List<DocumentCultureVariationDto>();
|
||||
foreach (var ev in editedLanguageVersions)
|
||||
{
|
||||
if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations))
|
||||
{
|
||||
//check if it needs updating
|
||||
if (docVariations.Edited != ev.Value)
|
||||
{
|
||||
docVariations.Edited = ev.Value;
|
||||
toUpdate.Add(docVariations);
|
||||
}
|
||||
}
|
||||
else if (ev.Key.langId.HasValue)
|
||||
{
|
||||
//This should never happen! If a property culture is flagged as edited then the culture must exist at the document level
|
||||
throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}");
|
||||
}
|
||||
}
|
||||
|
||||
//Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
|
||||
foreach (var editValue in toUpdate.GroupBy(x => x.Edited))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, editValue.Select(x => x.Id)));
|
||||
}
|
||||
|
||||
//Now bulk update the umbracoDocument table
|
||||
foreach (var editValue in editedDocument.GroupBy(x => x.Value))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentDto>(x => x.NodeId, editValue.Select(x => x.Key)));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row)
|
||||
{
|
||||
return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue
|
||||
|| !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue
|
||||
|| pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue
|
||||
|| pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue
|
||||
|| pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue;
|
||||
}
|
||||
|
||||
private class NameCompareDto
|
||||
{
|
||||
public int NodeId { get; set; }
|
||||
public int CurrentVersion { get; set; }
|
||||
public int LanguageId { get; set; }
|
||||
public string CurrentName { get; set; }
|
||||
public string PublishedName { get; set; }
|
||||
public int? PublishedVersion { get; set; }
|
||||
public int Id { get; set; } // the Id of the DocumentCultureVariationDto
|
||||
public bool Edited { get; set; }
|
||||
}
|
||||
|
||||
private class PropertyValueVersionDto
|
||||
{
|
||||
public int VersionId { get; set; }
|
||||
public int PropertyTypeId { get; set; }
|
||||
public int? LanguageId { get; set; }
|
||||
public string Segment { get; set; }
|
||||
public int? IntValue { get; set; }
|
||||
|
||||
private decimal? _decimalValue;
|
||||
[Column("decimalValue")]
|
||||
public decimal? DecimalValue
|
||||
{
|
||||
get => _decimalValue;
|
||||
set => _decimalValue = value?.Normalize();
|
||||
}
|
||||
|
||||
public DateTime? DateValue { get; set; }
|
||||
public string VarcharValue { get; set; }
|
||||
public string TextValue { get; set; }
|
||||
|
||||
public int NodeId { get; set; }
|
||||
public bool Current { get; set; }
|
||||
public bool Published { get; set; }
|
||||
|
||||
public byte Variations { get; set; }
|
||||
}
|
||||
|
||||
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
|
||||
|
||||
@@ -279,6 +279,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return moveInfo;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id)
|
||||
{
|
||||
if (id == default)
|
||||
return new Dictionary<Udi, IEnumerable<string>>();
|
||||
|
||||
var sql = Sql()
|
||||
.Select<ContentTypeDto>(ct => ct.Select(node => node.NodeDto))
|
||||
.AndSelect<PropertyTypeDto>(pt => Alias(pt.Alias, "ptAlias"), pt => Alias(pt.Name, "ptName"))
|
||||
.From<PropertyTypeDto>()
|
||||
.InnerJoin<ContentTypeDto>().On<ContentTypeDto, PropertyTypeDto>(ct => ct.NodeId, pt => pt.ContentTypeId)
|
||||
.InnerJoin<NodeDto>().On<NodeDto, ContentTypeDto>(n => n.NodeId, ct => ct.NodeId)
|
||||
.Where<PropertyTypeDto>(pt => pt.DataTypeId == id)
|
||||
.OrderBy<NodeDto>(node => node.NodeId)
|
||||
.AndBy<PropertyTypeDto>(pt => pt.Alias);
|
||||
|
||||
var dtos = Database.FetchOneToMany<ContentTypeReferenceDto>(ct => ct.PropertyTypes, sql);
|
||||
|
||||
return dtos.ToDictionary(
|
||||
x => (Udi)new GuidUdi(ObjectTypes.GetUdiType(x.NodeDto.NodeObjectType.Value), x.NodeDto.UniqueId).EnsureClosed(),
|
||||
x => (IEnumerable<string>)x.PropertyTypes.Select(p => p.Alias).ToList());
|
||||
}
|
||||
|
||||
private string EnsureUniqueNodeName(string nodeName, int id = 0)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql
|
||||
@@ -291,5 +313,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
return SimilarNodeName.GetUniqueName(names, id, nodeName);
|
||||
}
|
||||
|
||||
|
||||
[TableName(Constants.DatabaseSchema.Tables.ContentType)]
|
||||
private class ContentTypeReferenceDto : ContentTypeDto
|
||||
{
|
||||
[ResultColumn]
|
||||
[Reference(ReferenceType.Many)]
|
||||
public List<PropertyTypeReferenceDto> PropertyTypes { get; set; }
|
||||
}
|
||||
|
||||
[TableName(Constants.DatabaseSchema.Tables.PropertyType)]
|
||||
private class PropertyTypeReferenceDto
|
||||
{
|
||||
[Column("ptAlias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[Column("ptName")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,14 +248,63 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return dto == null ? null : MapDtoToContent(dto);
|
||||
}
|
||||
|
||||
// deletes a specific version
|
||||
public override void DeleteVersion(int versionId)
|
||||
{
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the version we want to delete
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersion", tsql =>
|
||||
tsql.Select<ContentVersionDto>()
|
||||
.AndSelect<DocumentVersionDto>()
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<DocumentVersionDto>()
|
||||
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
|
||||
.Where<ContentVersionDto>(x => x.Id == SqlTemplate.Arg<int>("versionId"))
|
||||
);
|
||||
var versionDto = Database.Fetch<DocumentVersionDto>(template.Sql(new { versionId })).FirstOrDefault();
|
||||
|
||||
// nothing to delete
|
||||
if (versionDto == null)
|
||||
return;
|
||||
|
||||
// don't delete the current or published version
|
||||
if (versionDto.ContentVersionDto.Current)
|
||||
throw new InvalidOperationException("Cannot delete the current version.");
|
||||
else if (versionDto.Published)
|
||||
throw new InvalidOperationException("Cannot delete the published version.");
|
||||
|
||||
PerformDeleteVersion(versionDto.ContentVersionDto.NodeId, versionId);
|
||||
}
|
||||
|
||||
// deletes all versions of an entity, older than a date.
|
||||
public override void DeleteVersions(int nodeId, DateTime versionDate)
|
||||
{
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the versions we want to delete, excluding the current one
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersions", tsql =>
|
||||
tsql.Select<ContentVersionDto>()
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<DocumentVersionDto>()
|
||||
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
|
||||
.Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg<DateTime>("versionDate"))
|
||||
.Where<DocumentVersionDto>( x => !x.Published)
|
||||
);
|
||||
var versionDtos = Database.Fetch<ContentVersionDto>(template.Sql(new { nodeId, versionDate }));
|
||||
foreach (var versionDto in versionDtos)
|
||||
PerformDeleteVersion(versionDto.NodeId, versionDto.Id);
|
||||
}
|
||||
|
||||
protected override void PerformDeleteVersion(int id, int versionId)
|
||||
{
|
||||
// raise event first else potential FK issues
|
||||
OnUowRemovingVersion(new ScopedVersionEventArgs(AmbientScope, id, versionId));
|
||||
|
||||
Database.Delete<PropertyDataDto>("WHERE versionId = @versionId", new { versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE id = @versionId", new { versionId });
|
||||
Database.Delete<ContentVersionCultureVariationDto>("WHERE versionId = @versionId", new { versionId });
|
||||
Database.Delete<DocumentVersionDto>("WHERE id = @versionId", new { versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE id = @versionId", new { versionId });
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -386,7 +435,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
@@ -511,7 +560,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
|
||||
|
||||
documentVersionDto.Published = false; // non-published version
|
||||
Database.Insert(documentVersionDto);
|
||||
Database.Insert(documentVersionDto);
|
||||
}
|
||||
|
||||
// replace the property data (rather than updating)
|
||||
@@ -571,7 +620,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
@@ -1297,25 +1346,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, HashSet<string> editedCultures)
|
||||
{
|
||||
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
|
||||
foreach (var culture in allCultures)
|
||||
yield return new DocumentCultureVariationDto
|
||||
{
|
||||
var dto = new DocumentCultureVariationDto
|
||||
{
|
||||
NodeId = content.Id,
|
||||
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
|
||||
Culture = culture,
|
||||
|
||||
Name = content.GetCultureName(culture) ?? content.GetPublishName(culture),
|
||||
|
||||
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
|
||||
|
||||
Available = content.IsCultureAvailable(culture),
|
||||
Published = content.IsCulturePublished(culture),
|
||||
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
|
||||
Edited = content.IsCultureAvailable(culture) &&
|
||||
(!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
|
||||
};
|
||||
|
||||
yield return dto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ContentVariation
|
||||
|
||||
@@ -42,8 +42,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
var isMember = objectType == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, x =>
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, x =>
|
||||
{
|
||||
if (filter == null) return;
|
||||
foreach (var filterClause in filter.GetWhereClauses())
|
||||
@@ -54,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
|
||||
sql = translator.Translate();
|
||||
sql = AddGroupBy(isContent, isMedia, sql, ordering.IsEmpty);
|
||||
sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty);
|
||||
|
||||
if (!ordering.IsEmpty)
|
||||
{
|
||||
@@ -81,6 +82,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
dtos = page.Items;
|
||||
totalRecords = page.TotalItems;
|
||||
}
|
||||
else if (isMember)
|
||||
{
|
||||
var page = Database.Page<MemberEntityDto>(pageIndexToFetch, pageSize, sql);
|
||||
dtos = page.Items;
|
||||
totalRecords = page.TotalItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
var page = Database.Page<BaseDto>(pageIndexToFetch, pageSize, sql);
|
||||
@@ -88,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
totalRecords = page.TotalItems;
|
||||
}
|
||||
|
||||
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray();
|
||||
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray();
|
||||
|
||||
if (isContent)
|
||||
BuildVariants(entities.Cast<DocumentEntitySlim>());
|
||||
@@ -98,13 +105,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public IEntitySlim Get(Guid key)
|
||||
{
|
||||
var sql = GetBaseWhere(false, false, false, key);
|
||||
var sql = GetBaseWhere(false, false, false, false, key);
|
||||
var dto = Database.FirstOrDefault<BaseDto>(sql);
|
||||
return dto == null ? null : BuildEntity(false, false, dto);
|
||||
return dto == null ? null : BuildEntity(false, false, false, dto);
|
||||
}
|
||||
|
||||
|
||||
private IEntitySlim GetEntity(Sql<ISqlContext> sql, bool isContent, bool isMedia)
|
||||
private IEntitySlim GetEntity(Sql<ISqlContext> sql, bool isContent, bool isMedia, bool isMember)
|
||||
{
|
||||
//isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
if (isContent)
|
||||
@@ -120,7 +127,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
if (dto == null) return null;
|
||||
|
||||
var entity = BuildEntity(false, isMedia, dto);
|
||||
var entity = BuildEntity(false, isMedia, isMember, dto);
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -129,25 +136,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectTypeId == Constants.ObjectTypes.Media;
|
||||
var isMember = objectTypeId == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, key);
|
||||
return GetEntity(sql, isContent, isMedia);
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, key);
|
||||
return GetEntity(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public IEntitySlim Get(int id)
|
||||
{
|
||||
var sql = GetBaseWhere(false, false, false, id);
|
||||
var sql = GetBaseWhere(false, false, false, false, id);
|
||||
var dto = Database.FirstOrDefault<BaseDto>(sql);
|
||||
return dto == null ? null : BuildEntity(false, false, dto);
|
||||
return dto == null ? null : BuildEntity(false, false, false, dto);
|
||||
}
|
||||
|
||||
public IEntitySlim Get(int id, Guid objectTypeId)
|
||||
{
|
||||
var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectTypeId == Constants.ObjectTypes.Media;
|
||||
var isMember = objectTypeId == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, id);
|
||||
return GetEntity(sql, isContent, isMedia);
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, id);
|
||||
return GetEntity(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids)
|
||||
@@ -164,7 +173,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
: PerformGetAll(objectType);
|
||||
}
|
||||
|
||||
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia)
|
||||
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia, bool isMember)
|
||||
{
|
||||
//isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
if (isContent)
|
||||
@@ -180,7 +189,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
? (IEnumerable<BaseDto>)Database.Fetch<MediaEntityDto>(sql)
|
||||
: Database.Fetch<BaseDto>(sql);
|
||||
|
||||
var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray();
|
||||
var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray();
|
||||
|
||||
return entities;
|
||||
}
|
||||
@@ -189,9 +198,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
var isMember = objectType == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter);
|
||||
return GetEntities(sql, isContent, isMedia);
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectType, filter);
|
||||
return GetEntities(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public IEnumerable<TreeEntityPath> GetAllPaths(Guid objectType, params int[] ids)
|
||||
@@ -218,26 +228,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query)
|
||||
{
|
||||
var sqlClause = GetBase(false, false, null);
|
||||
var sqlClause = GetBase(false, false, false, null);
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
sql = AddGroupBy(false, false, sql, true);
|
||||
sql = AddGroupBy(false, false, false, sql, true);
|
||||
var dtos = Database.Fetch<BaseDto>(sql);
|
||||
return dtos.Select(x => BuildEntity(false, false, x)).ToList();
|
||||
return dtos.Select(x => BuildEntity(false, false, false, x)).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType)
|
||||
{
|
||||
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
|
||||
var isMedia = objectType == Constants.ObjectTypes.Media;
|
||||
var isMember = objectType == Constants.ObjectTypes.Member;
|
||||
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, null, objectType);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType);
|
||||
|
||||
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
|
||||
sql = translator.Translate();
|
||||
sql = AddGroupBy(isContent, isMedia, sql, true);
|
||||
sql = AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
|
||||
return GetEntities(sql, isContent, isMedia);
|
||||
return GetEntities(sql, isContent, isMedia, isMember);
|
||||
}
|
||||
|
||||
public UmbracoObjectTypes GetObjectType(int id)
|
||||
@@ -329,29 +340,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
// gets the full sql for a given object type and a given unique id
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId)
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Guid uniqueId)
|
||||
{
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, objectType, uniqueId);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, uniqueId);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the full sql for a given object type and a given node id
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, int nodeId)
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, int nodeId)
|
||||
{
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, objectType, nodeId);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, nodeId);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the full sql for a given object type, with a given filter
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Action<Sql<ISqlContext>> filter)
|
||||
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action<Sql<ISqlContext>> filter)
|
||||
{
|
||||
var sql = GetBaseWhere(isContent, isMedia, false, filter, objectType);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM [+ filter] sql
|
||||
// always from the 'current' content version
|
||||
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, Action<Sql<ISqlContext>> filter, bool isCount = false)
|
||||
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, bool isMember, Action<Sql<ISqlContext>> filter, bool isCount = false)
|
||||
{
|
||||
var sql = Sql();
|
||||
|
||||
@@ -366,7 +377,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
.AndSelect<NodeDto>(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate)
|
||||
.Append(", COUNT(child.id) AS children");
|
||||
|
||||
if (isContent || isMedia)
|
||||
if (isContent || isMedia || isMember)
|
||||
sql
|
||||
.AndSelect<ContentVersionDto>(x => Alias(x.Id, "versionId"))
|
||||
.AndSelect<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
|
||||
@@ -387,7 +398,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
sql
|
||||
.From<NodeDto>();
|
||||
|
||||
if (isContent || isMedia)
|
||||
if (isContent || isMedia || isMember)
|
||||
{
|
||||
sql
|
||||
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
|
||||
@@ -404,7 +415,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (isMedia)
|
||||
{
|
||||
sql
|
||||
.InnerJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
.LeftJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
}
|
||||
|
||||
//Any LeftJoin statements need to come last
|
||||
@@ -422,49 +433,49 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// gets the base SELECT + FROM [+ filter] + WHERE sql
|
||||
// for a given object type, with a given filter
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
|
||||
{
|
||||
return GetBase(isContent, isMedia, filter, isCount)
|
||||
return GetBase(isContent, isMedia, isMember, filter, isCount)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == objectType);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given node id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, int id)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, int id)
|
||||
{
|
||||
var sql = GetBase(isContent, isMedia, null, isCount)
|
||||
var sql = GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.NodeId == id);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given unique id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid uniqueId)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid uniqueId)
|
||||
{
|
||||
var sql = GetBase(isContent, isMedia, null, isCount)
|
||||
var sql = GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.UniqueId == uniqueId);
|
||||
return AddGroupBy(isContent, isMedia, sql, true);
|
||||
return AddGroupBy(isContent, isMedia, isMember, sql, true);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given object type and node id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, int nodeId)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, int nodeId)
|
||||
{
|
||||
return GetBase(isContent, isMedia, null, isCount)
|
||||
return GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.NodeId == nodeId && x.NodeObjectType == objectType);
|
||||
}
|
||||
|
||||
// gets the base SELECT + FROM + WHERE sql
|
||||
// for a given object type and unique id
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, Guid uniqueId)
|
||||
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, Guid uniqueId)
|
||||
{
|
||||
return GetBase(isContent, isMedia, null, isCount)
|
||||
return GetBase(isContent, isMedia, isMember, null, isCount)
|
||||
.Where<NodeDto>(x => x.UniqueId == uniqueId && x.NodeObjectType == objectType);
|
||||
}
|
||||
|
||||
// gets the GROUP BY / ORDER BY sql
|
||||
// required in order to count children
|
||||
protected Sql<ISqlContext> AddGroupBy(bool isContent, bool isMedia, Sql<ISqlContext> sql, bool defaultSort)
|
||||
protected Sql<ISqlContext> AddGroupBy(bool isContent, bool isMedia, bool isMember, Sql<ISqlContext> sql, bool defaultSort)
|
||||
{
|
||||
sql
|
||||
.GroupBy<NodeDto>(x => x.NodeId, x => x.Trashed, x => x.ParentId, x => x.UserId, x => x.Level, x => x.Path)
|
||||
@@ -483,7 +494,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
|
||||
if (isContent || isMedia)
|
||||
if (isContent || isMedia || isMember)
|
||||
sql
|
||||
.AndBy<ContentVersionDto>(x => x.Id)
|
||||
.AndBy<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
|
||||
@@ -528,6 +539,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
public string MediaPath { get; set; }
|
||||
}
|
||||
|
||||
private class MemberEntityDto : BaseDto
|
||||
{
|
||||
}
|
||||
|
||||
public class VariantInfoDto
|
||||
{
|
||||
public int NodeId { get; set; }
|
||||
@@ -574,12 +589,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
#region Factory
|
||||
|
||||
private EntitySlim BuildEntity(bool isContent, bool isMedia, BaseDto dto)
|
||||
private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto)
|
||||
{
|
||||
if (isContent)
|
||||
return BuildDocumentEntity(dto);
|
||||
if (isMedia)
|
||||
return BuildMediaEntity(dto);
|
||||
if (isMember)
|
||||
return BuildMemberEntity(dto);
|
||||
|
||||
// EntitySlim does not track changes
|
||||
var entity = new EntitySlim();
|
||||
@@ -644,6 +661,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return entity;
|
||||
}
|
||||
|
||||
private MemberEntitySlim BuildMemberEntity(BaseDto dto)
|
||||
{
|
||||
// EntitySlim does not track changes
|
||||
var entity = new MemberEntitySlim();
|
||||
BuildEntity(entity, dto);
|
||||
|
||||
entity.ContentTypeAlias = dto.Alias;
|
||||
entity.ContentTypeIcon = dto.Icon;
|
||||
entity.ContentTypeThumbnail = dto.Thumbnail;
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,17 +102,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
|
||||
var list = new List<string>
|
||||
{
|
||||
//NOTE: There is no constraint between the Language and cmsDictionary/cmsLanguageText tables (?)
|
||||
// but we still need to remove them
|
||||
"DELETE FROM cmsLanguageText WHERE languageId = @id",
|
||||
"DELETE FROM umbracoPropertyData WHERE languageId = @id",
|
||||
"DELETE FROM umbracoContentVersionCultureVariation WHERE languageId = @id",
|
||||
"DELETE FROM umbracoDocumentCultureVariation WHERE languageId = @id",
|
||||
"DELETE FROM umbracoLanguage WHERE id = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id"
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.DictionaryValue + " WHERE languageId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE languageId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE languageId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentCultureVariation + " WHERE languageId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.TagRelationship + " WHERE tagId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id)",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.Language + " WHERE id = @id"
|
||||
};
|
||||
return list;
|
||||
}
|
||||
@@ -182,6 +182,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(ILanguage.IsoCode)))
|
||||
{
|
||||
//if the iso code is changing, ensure there's not another lang with the same code already assigned
|
||||
var sameCode = Sql()
|
||||
.SelectCount()
|
||||
.From<LanguageDto>()
|
||||
.Where<LanguageDto>(x => x.IsoCode == entity.IsoCode && x.Id != entity.Id);
|
||||
|
||||
var countOfSameCode = Database.ExecuteScalar<int>(sameCode);
|
||||
if (countOfSameCode > 0)
|
||||
throw new InvalidOperationException($"Cannot update the language to a new culture: {entity.IsoCode} since that culture is already assigned to another language entity.");
|
||||
}
|
||||
|
||||
// fallback cycles are detected at service level
|
||||
|
||||
// update
|
||||
@@ -250,7 +263,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
lock (_codeIdMap)
|
||||
{
|
||||
if (_codeIdMap.TryGetValue(isoCode, out var id)) return id;
|
||||
if (isoCode.Contains('-') && _codeIdMap.TryGetValue(isoCode.Split('-').First(), out var invariantId)) return invariantId;
|
||||
}
|
||||
if (throwOnNotFound)
|
||||
throw new ArgumentException($"Code {isoCode} does not correspond to an existing language.", nameof(isoCode));
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, IMediaTypeRepository
|
||||
{
|
||||
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
|
||||
@@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// the cache policy will always want everything
|
||||
// even GetMany(ids) gets everything and filters afterwards
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
if (ids.Any()) throw new PanicException("There can be no ids specified");
|
||||
return CommonRepository.GetAllTypes().OfType<IMediaType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
|
||||
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
|
||||
// types by default on the document and media repo's so we can query by content type there too.
|
||||
// types by default on the document and media repos so we can query by content type there too.
|
||||
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>(left => left.ContentTypeId, right => right.NodeId);
|
||||
|
||||
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
@@ -546,6 +546,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (ordering.OrderBy.InvariantEquals("userName"))
|
||||
return SqlSyntax.GetFieldName<MemberDto>(x => x.LoginName);
|
||||
|
||||
if (ordering.OrderBy.InvariantEquals("updateDate"))
|
||||
return SqlSyntax.GetFieldName<ContentVersionDto>(x => x.VersionDate);
|
||||
|
||||
if (ordering.OrderBy.InvariantEquals("createDate"))
|
||||
return SqlSyntax.GetFieldName<NodeDto>(x => x.CreateDate);
|
||||
|
||||
if (ordering.OrderBy.InvariantEquals("contentTypeAlias"))
|
||||
return SqlSyntax.GetFieldName<ContentTypeDto>(x => x.Alias);
|
||||
|
||||
return base.ApplySystemOrdering(ref sql, ordering);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, IMemberTypeRepository
|
||||
{
|
||||
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
|
||||
@@ -57,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// the cache policy will always want everything
|
||||
// even GetMany(ids) gets everything and filters afterwards
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
if (ids.Any()) throw new PanicException("There can be no ids specified");
|
||||
return CommonRepository.GetAllTypes().OfType<IMemberType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -105,7 +106,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
|
||||
CreateDateUtc = redirectUrl.CreateDateUtc,
|
||||
Url = redirectUrl.Url,
|
||||
Culture = redirectUrl.Culture,
|
||||
UrlHash = redirectUrl.Url.ToSHA1()
|
||||
UrlHash = redirectUrl.Url.GenerateHash<SHA1>()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,7 +135,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
|
||||
|
||||
public IRedirectUrl Get(string url, Guid contentKey, string culture)
|
||||
{
|
||||
var urlHash = url.ToSHA1();
|
||||
var urlHash = url.GenerateHash<SHA1>();
|
||||
var sql = GetBaseQuery(false).Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture);
|
||||
var dto = Database.Fetch<RedirectUrlDto>(sql).FirstOrDefault();
|
||||
return dto == null ? null : Map(dto);
|
||||
@@ -157,7 +158,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
|
||||
|
||||
public IRedirectUrl GetMostRecentUrl(string url)
|
||||
{
|
||||
var urlHash = url.ToSHA1();
|
||||
var urlHash = url.GenerateHash<SHA1>();
|
||||
var sql = GetBaseQuery(false)
|
||||
.Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash)
|
||||
.OrderByDescending<RedirectUrlDto>(x => x.CreateDateUtc);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text.RegularExpressions;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
@@ -76,6 +77,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
string ConvertIntegerToOrderableString { get; }
|
||||
string ConvertDateToOrderableString { get; }
|
||||
string ConvertDecimalToOrderableString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default isolation level for the database
|
||||
/// </summary>
|
||||
IsolationLevel DefaultIsolationLevel { get; }
|
||||
|
||||
IEnumerable<string> GetTablesInSchema(IDatabase db);
|
||||
IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db);
|
||||
@@ -121,5 +127,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
/// unspecified.</para>
|
||||
/// </remarks>
|
||||
bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
|
||||
|
||||
void ReadLock(IDatabase db, params int[] lockIds);
|
||||
void WriteLock(IDatabase db, params int[] lockIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
@@ -52,6 +54,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return "(" + string.Join("+", args) + ")";
|
||||
}
|
||||
|
||||
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead;
|
||||
|
||||
public override string FormatColumnRename(string tableName, string oldName, string newName)
|
||||
{
|
||||
//NOTE Sql CE doesn't support renaming a column, so a new column needs to be created, then copy data and finally remove old column
|
||||
@@ -152,6 +156,39 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
public override void WriteLock(IDatabase db, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
|
||||
db.Execute(@"SET LOCK_TIMEOUT 1800;");
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
var i = db.Execute(@"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
|
||||
if (i == 0) // ensure we are actually locking!
|
||||
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReadLock(IDatabase db, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
|
||||
if (i == null) // ensure we are actually locking!
|
||||
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override string FormatIdentity(ColumnDefinition column)
|
||||
{
|
||||
return column.IsIdentity ? GetIdentityString(column) : string.Empty;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -179,6 +180,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return items.Select(x => x.TABLE_NAME).Cast<string>().ToList();
|
||||
}
|
||||
|
||||
public override IsolationLevel DefaultIsolationLevel => IsolationLevel.ReadCommitted;
|
||||
|
||||
public override IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db)
|
||||
{
|
||||
var items = db.Fetch<dynamic>("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
|
||||
@@ -246,6 +249,41 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
public override void WriteLock(IDatabase db, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
|
||||
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
|
||||
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
db.Execute(@"SET LOCK_TIMEOUT 1800;");
|
||||
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
|
||||
if (i == 0) // ensure we are actually locking!
|
||||
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void ReadLock(IDatabase db, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
|
||||
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new { id = lockId });
|
||||
if (i == null) // ensure we are actually locking!
|
||||
throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockIds));
|
||||
}
|
||||
}
|
||||
|
||||
public override string FormatColumnRename(string tableName, string oldName, string newName)
|
||||
{
|
||||
return string.Format(RenameColumn, tableName, oldName, newName);
|
||||
|
||||
@@ -200,7 +200,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
|
||||
return "NVARCHAR";
|
||||
}
|
||||
|
||||
|
||||
public abstract IsolationLevel DefaultIsolationLevel { get; }
|
||||
|
||||
public virtual IEnumerable<string> GetTablesInSchema(IDatabase db)
|
||||
{
|
||||
return new List<string>();
|
||||
@@ -225,6 +227,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
|
||||
public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
|
||||
|
||||
public abstract void ReadLock(IDatabase db, params int[] lockIds);
|
||||
public abstract void WriteLock(IDatabase db, params int[] lockIds);
|
||||
|
||||
public virtual bool DoesTableExist(IDatabase db, string tableName)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -20,9 +20,6 @@ namespace Umbraco.Core.Persistence
|
||||
/// </remarks>
|
||||
public class UmbracoDatabase : Database, IUmbracoDatabase
|
||||
{
|
||||
// Umbraco's default isolation level is RepeatableRead
|
||||
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly RetryPolicy _connectionRetryPolicy;
|
||||
private readonly RetryPolicy _commandRetryPolicy;
|
||||
@@ -38,7 +35,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
|
||||
/// </remarks>
|
||||
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
|
||||
: base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel)
|
||||
: base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
|
||||
{
|
||||
SqlContext = sqlContext;
|
||||
|
||||
@@ -54,7 +51,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// </summary>
|
||||
/// <remarks>Internal for unit tests only.</remarks>
|
||||
internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger)
|
||||
: base(connection, sqlContext.DatabaseType, DefaultIsolationLevel)
|
||||
: base(connection, sqlContext.DatabaseType, sqlContext.SqlSyntax.DefaultIsolationLevel)
|
||||
{
|
||||
SqlContext = sqlContext;
|
||||
_logger = logger;
|
||||
|
||||
@@ -32,7 +32,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Web")]
|
||||
|
||||
// Umbraco Headless
|
||||
[assembly: InternalsVisibleTo("Umbraco.Headless")]
|
||||
[assembly: InternalsVisibleTo("Umbraco.Cloud.Headless")]
|
||||
|
||||
// code analysis
|
||||
// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
public static partial class ConfigurationFieldsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a configuration field.
|
||||
/// </summary>
|
||||
/// <param name="fields">The list of configuration fields.</param>
|
||||
/// <param name="key">The key (alias) of the field.</param>
|
||||
/// <param name="name">The name (label) of the field.</param>
|
||||
/// <param name="description">The description for the field.</param>
|
||||
/// <param name="view">The path to the editor view to be used for the field.</param>
|
||||
/// <param name="config">Optional configuration used for field's editor.</param>
|
||||
public static void Add(
|
||||
this List<ConfigurationField> fields,
|
||||
string key,
|
||||
string name,
|
||||
string description,
|
||||
string view,
|
||||
IDictionary<string, object> config = null)
|
||||
{
|
||||
fields.Add(new ConfigurationField
|
||||
{
|
||||
Key = key,
|
||||
Name = name,
|
||||
Description = description,
|
||||
View = view,
|
||||
Config = config,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
// defaults
|
||||
Type = type;
|
||||
Icon = Constants.Icons.PropertyEditor;
|
||||
Group = "common";
|
||||
Group = Constants.PropertyEditors.Groups.Common;
|
||||
|
||||
// assign properties based on the attribute, if it is found
|
||||
Attribute = GetType().GetCustomAttribute<DataEditorAttribute>(false);
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// Gets or sets an optional group.
|
||||
/// </summary>
|
||||
/// <remarks>The group can be used for example to group the editors by category.</remarks>
|
||||
public string Group { get; set; } = "common";
|
||||
public string Group { get; set; } = Constants.PropertyEditors.Groups.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the value editor is deprecated.
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Umbraco.Core.PropertyEditors
|
||||
[JsonProperty("name", Required = Required.Always)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("nameTemplate")]
|
||||
public string NameTemplate { get; set; }
|
||||
|
||||
[JsonProperty("alias", Required = Required.Always)]
|
||||
public string Alias { get; set; }
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <summary>
|
||||
/// Represents a property editor for label properties.
|
||||
/// </summary>
|
||||
[DataEditor(Constants.PropertyEditors.Aliases.Label, "Label", "readonlyvalue", Icon = "icon-readonly")]
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.Label,
|
||||
"Label",
|
||||
"readonlyvalue",
|
||||
Icon = "icon-readonly")]
|
||||
public class LabelPropertyEditor : DataEditor
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <summary>
|
||||
/// Gets the type of the dynamic configuration provider.
|
||||
/// </summary>
|
||||
//TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562
|
||||
public Type TagsConfigurationProviderType { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
if (source is decimal sourceDecimal) return sourceDecimal;
|
||||
if (source is string sourceDecimalString)
|
||||
return decimal.TryParse(sourceDecimalString, NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : 0;
|
||||
if (source is double sourceDouble)
|
||||
return Convert.ToDecimal(sourceDouble);
|
||||
return (decimal) 0;
|
||||
case ValueTypes.Integer:
|
||||
if (source is int sourceInt) return sourceInt;
|
||||
|
||||
@@ -103,18 +103,26 @@ namespace Umbraco.Core
|
||||
/// Gets the children of the content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="culture">The specific culture to get the url children for. If null is used the current culture is used (Default is null).</param>
|
||||
/// <param name="culture">
|
||||
/// The specific culture to get the url children for. Default is null which will use the current culture in <see cref="VariationContext"/>
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// <para>Gets children that are available for the specified culture.</para>
|
||||
/// <para>Children are sorted by their sortOrder.</para>
|
||||
/// <para>The '*' culture and supported and returns everything.</para>
|
||||
/// <para>
|
||||
/// For culture,
|
||||
/// if null is used the current culture is used.
|
||||
/// If an empty string is used only invariant children are returned.
|
||||
/// If "*" is used all children are returned.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If a variant culture is specified or there is a current culture in the <see cref="VariationContext"/> then the Children returned
|
||||
/// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture.
|
||||
/// However, if an empty string is specified only invariant children are returned.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, string culture = null)
|
||||
{
|
||||
// invariant has invariant value (whatever the requested culture)
|
||||
if (!content.ContentType.VariesByCulture() && culture != "*")
|
||||
culture = "";
|
||||
|
||||
// handle context culture for variant
|
||||
if (culture == null)
|
||||
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user