diff --git a/.github/BUILD.md b/.github/BUILD.md index a2406c58bd..c89a1be460 100644 --- a/.github/BUILD.md +++ b/.github/BUILD.md @@ -1,14 +1,176 @@ -Umbraco Cms Build --- ----- +# Umbraco Cms Build -# Quick! +## Are you sure? -To build Umbraco, fire PowerShell and move to Umbraco's repository root (the directory that contains `src`, `build`, `README.md`...). There, trigger the build with the following command: +In order to use Umbraco as a CMS and build your website with it, you should not build it yourself. If you're reading this then you're trying to contribute to Umbraco or you're debugging a complex issue. + +- Are you about to create a pull request for Umbraco? +- Are you trying to get to the bottom of a problem in your existing Umbraco installation? + +If the answer is yes, please read on. Otherwise, make sure to head on over [to the download page](https://our.umbraco.com/download) and start using Umbraco CMS as intended. + +**Table of contents** + +[Building from source](#building-from-source) + * [The quick build](#quick) + * [Build infrastructure](#build-infrastructure) + * [Properties](#properties) + * [GetUmbracoVersion](#getumbracoversion) + * [SetUmbracoVersion](#setumbracoversion) + * [Build](#build) + * [Build-UmbracoDocs](#build-umbracodocs) + * [Verify-NuGet](#verify-nuget) + * [Cleaning up](#cleaning-up) + +[Azure DevOps](#azure-devops) + +[Quirks](#quirks) + * [Powershell quirks](#powershell-quirks) + * [Git quirks](#git-quirks) + + +## Building from source + +Did you read ["Are you sure"](#are-you-sure)? + +### Quick! + +To build Umbraco, fire up PowerShell and move to Umbraco's repository root (the directory that contains `src`, `build`, `LICENSE.md`...). There, trigger the build with the following command: build/build.ps1 -## PowerShell Quirks +You might run into [Powershell quirks](#powershell-quirks). + +### Build Infrastructure + +The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with: + + $ubuild = build/build.ps1 -get + +The object exposes various properties and methods that can be used to fine-grain build Umbraco. Some, but not all, of them are detailed below. + +#### Properties + +The object exposes the following properties: + +* `SolutionRoot`: the absolute path to the solution root +* `VisualStudio`: a Visual Studio object (see below) +* `NuGet`: the absolute path to the NuGet executable +* `Zip`: the absolute path to the 7Zip executable +* `VsWhere`: the absolute path to the VsWhere executable +* `NodePath`: the absolute path to the Node install +* `NpmPath`: the absolute path to the Npm install + +The Visual Studio object is `null` when Visual Studio has not been detected (eg on VSTS). When not null, the object exposes the following properties: + +* `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 + +#### GetUmbracoVersion + +Gets an object representing the current Umbraco version. Example: + + $v = $ubuild.GetUmbracoVersion() + Write-Host $v.Semver + +The object exposes the following properties: + +* `Semver`: the semver object representing the version +* `Release`: the main part of the version (eg `7.6.33`) +* `Comment`: the pre release part of the version (eg `alpha02`) +* `Build`: the build number part of the version (eg `1234`) + +#### SetUmbracoVersion + +Modifies Umbraco files with the new version. + +>This entirely replaces the legacy `UmbracoVersion.txt` file. Do *not* edit version infos in files. + +The version must be a valid semver version. It can include a *pre release* part (eg `alpha02`) and/or a *build number* (eg `1234`). Examples: + + $ubuild.SetUmbracoVersion("7.6.33") + $ubuild.SetUmbracoVersion("7.6.33-alpha.2") + $ubuild.SetUmbracoVersion("7.6.33+1234") + $ubuild.SetUmbracoVersion("7.6.33-beta.5+5678") + +#### Build + +Builds Umbraco. Temporary files are generated in `build.tmp` while the actual artifacts (zip files, NuGet packages...) are produced in `build.out`. Example: + + $ubuild.Build() + +Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. + +**Note: web.config** + +Building Umbraco requires a clean `web.config` file in the `Umbraco.Web.UI` project. If a `web.config` file already exists, the `pre-build` task (see below) will save it as `web.config.temp-build` and replace it with a clean copy of `web.Template.config`. The original file is replaced once it is safe to do so, by the `pre-packages` task. + +#### Build-UmbracoDocs + +Builds umbraco documentation. Temporary files are generated in `build.tmp` while the actual artifacts (docs...) are produced in `build.out`. Example: + + Build-UmbracoDocs + +Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. + +#### Verify-NuGet + +Verifies that projects all require the same version of their dependencies, and that NuSpec files require versions that are consistent with projects. Example: + + Verify-NuGet + +### Cleaning up + +Once the solution has been used to run a site, one may want to "reset" the solution in order to run a fresh new site again. + +At the very minimum, you want + + git clean -Xdf src/Umbraco.Web.UI/App_Data + rm src/Umbraco.Web.UI/web.config + +Then, a simple 'Rebuild All' in Visual Studio will recreate a fresh `web.config` but should be quite fast (since it does not really need to rebuild anything). + +The `clean` Git command force (`-f`) removes (`-X`, note the capital X) all files and directories (`-d`) that are ignored by Git. + +This will leave media files and views around, but in most cases, it will be enough. + +To perform a more complete clear, you will want to also delete the content of the media, views, scripts... directories. + +The following command will force remove all untracked files and directories, whether they are ignored by Git or not. Combined with `git reset` it can recreate a pristine working directory. + + git clean -xdf . + +For git documentation see: +* git [clean]() +* git [reset]() + +## Azure DevOps + +Umbraco uses Azure DevOps for continuous integration, nightly builds and release builds. The Umbraco CMS project on DevOps [is available for anonymous users](https://umbraco.visualstudio.com/Umbraco%20Cms). + +DevOps uses the `Build-Umbraco` command several times, each time passing a different *target* parameter. The supported targets are: + +* `pre-build`: prepares the build +* `compile-belle`: compiles Belle +* `compile-umbraco`: compiles Umbraco +* `pre-tests`: prepares the tests +* `compile-tests`: compiles the tests +* `pre-packages`: prepares the packages +* `pkg-zip`: creates the zip files +* `pre-nuget`: prepares NuGet packages +* `pkg-nuget`: creates NuGet packages + +All these targets are executed when `Build-Umbraco` is invoked without a parameter (or with the `all` parameter). On VSTS, compilations (of Umbraco and tests) are performed by dedicated DevOps tasks. Similarly, creating the NuGet packages is also performed by dedicated DevOps tasks. + +Finally, the produced artifacts are published in two containers that can be downloaded from DevOps: `zips` contains the zip files while `nuget` contains the NuGet packages. + +>During a DevOps build, some environment `UMBRACO_*` variables are exported by the `pre-build` target and can be reused in other targets *and* in DevOps tasks. The `UMBRACO_TMP` environment variable is used in `Umbraco.Tests` to disable some tests that have issues with DevOps at the moment. + +## Quirks + +### PowerShell Quirks There is a good chance that running `build.ps1` ends up in error, with messages such as @@ -45,120 +207,6 @@ The best solution is to unblock the Zip file before un-zipping: right-click the PS> Get-ChildItem -Recurse *.* | Unblock-File -## Git Quirks +### Git Quirks -Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details). - -# Build Infrastructure - -The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with: - - $ubuild = build/build.ps1 -get - -The object exposes various properties and methods that can be used to fine-grain build Umbraco. Some, but not all, of them are detailed below. - -## Properties - -The object exposes the following properties: - -* `SolutionRoot`: the absolute path to the solution root -* `VisualStudio`: a Visual Studio object (see below) -* `NuGet`: the absolute path to the NuGet executable -* `Zip`: the absolute path to the 7Zip executable -* `VsWhere`: the absolute path to the VsWhere executable -* `NodePath`: the absolute path to the Node install -* `NpmPath`: the absolute path to the Npm install - -The Visual Studio object is `null` when Visual Studio has not been detected (eg on VSTS). When not null, the object exposes the following properties: - -* `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 - -## GetUmbracoVersion - -Gets an object representing the current Umbraco version. Example: - - $v = $ubuild.GetUmbracoVersion() - Write-Host $v.Semver - -The object exposes the following properties: - -* `Semver`: the semver object representing the version -* `Release`: the main part of the version (eg `7.6.33`) -* `Comment`: the pre release part of the version (eg `alpha02`) -* `Build`: the build number part of the version (eg `1234`) - -## SetUmbracoVersion - -Modifies Umbraco files with the new version. - ->This entirely replaces the legacy `UmbracoVersion.txt` file. Do *not* edit version infos in files. - -The version must be a valid semver version. It can include a *pre release* part (eg `alpha02`) and/or a *build number* (eg `1234`). Examples: - - $ubuild.SetUmbracoVersion("7.6.33") - $ubuild.SetUmbracoVersion("7.6.33-alpha.2") - $ubuild.SetUmbracoVersion("7.6.33+1234") - $ubuild.SetUmbracoVersion("7.6.33-beta.5+5678") - -## Build - -Builds Umbraco. Temporary files are generated in `build.tmp` while the actual artifacts (zip files, NuGet packages...) are produced in `build.out`. Example: - - $ubuild.Build() - -Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. - -### web.config - -Building Umbraco requires a clean `web.config` file in the `Umbraco.Web.UI` project. If a `web.config` file already exists, the `pre-build` task (see below) will save it as `web.config.temp-build` and replace it with a clean copy of `web.Template.config`. The original file is replaced once it is safe to do so, by the `pre-packages` task. - -## Build-UmbracoDocs - -Builds umbraco documentation. Temporary files are generated in `build.tmp` while the actual artifacts (docs...) are produced in `build.out`. Example: - - Build-UmbracoDocs - -Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. - -## Verify-NuGet - -Verifies that projects all require the same version of their dependencies, and that NuSpec files require versions that are consistent with projects. Example: - - Verify-NuGet - -# VSTS - -Continuous integration, nightly builds and release builds run on VSTS. - -VSTS uses the `Build-Umbraco` command several times, each time passing a different *target* parameter. The supported targets are: - -* `pre-build`: prepares the build -* `compile-belle`: compiles Belle -* `compile-umbraco`: compiles Umbraco -* `pre-tests`: prepares the tests -* `compile-tests`: compiles the tests -* `pre-packages`: prepares the packages -* `pkg-zip`: creates the zip files -* `pre-nuget`: prepares NuGet packages -* `pkg-nuget`: creates NuGet packages - -All these targets are executed when `Build-Umbraco` is invoked without a parameter (or with the `all` parameter). On VSTS, compilations (of Umbraco and tests) are performed by dedicated VSTS tasks. Similarly, creating the NuGet packages is also performed by dedicated VSTS tasks. - -Finally, the produced artifacts are published in two containers that can be downloaded from VSTS: `zips` contains the zip files while `nuget` contains the NuGet packages. - ->During a VSTS build, some environment `UMBRACO_*` variables are exported by the `pre-build` target and can be reused in other targets *and* in VSTS tasks. The `UMBRACO_TMP` environment variable is used in `Umbraco.Tests` to disable some tests that have issues with VSTS at the moment. - -# Notes - -*This part needs to be cleaned up* - -Nightlies should use some sort of build number. - -We should increment versions as soon as a version is released. Ie, as soon as `7.6.33` is released, we should `Set-UmbracoVersion 7.6.34-alpha` and push. - -NuGet / NuSpec consistency checks are performed in tests. We should move it so it is done as part of the PowerShell script even before we try to compile and run the tests. - -/eof \ No newline at end of file +Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details). \ No newline at end of file diff --git a/.github/CLEAR.md b/.github/CLEAR.md deleted file mode 100644 index 84dc0b46d7..0000000000 --- a/.github/CLEAR.md +++ /dev/null @@ -1,34 +0,0 @@ -Umbraco Cms Clear --- ----- - -Once the solution has been used to run a site, one may want to "reset" the solution in order to run a fresh new site again. - -## Fast - -At the very minimum, you want - - git clean -Xdf src/Umbraco.Web.UI/App_Data - rm src/Umbraco.Web.UI/web.config - -Then, a simple 'Rebuild All' in Visual Studio will recreate a fresh `web.config` but should be quite fast (since it does not really need to rebuild anything). - -The `clean` Git command force (`-f`) removes (`-X`, note the capital X) all files and directories (`-d`) that are ignored by Git. - -This will leave medias and views around, but in most cases, it will be enough. - -## More - -To perform a more complete clear, you will want to also delete the content of the media, views, masterpages, scripts... directories. - -## Full - -The following command will force remove all untracked files and directories, be they ignored by Git or not. Combined with `git reset` it can recreate a pristine working directory. - - git clean -xdf . - -## Docs - -See -* git [clean]() -* git [reset]() \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9dc6f9457f..84115b946a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,97 +1,187 @@ -_Looking for Umbraco version 8? [Click here](https://github.com/umbraco/Umbraco-CMS/blob/temp8/.github/V8_GETTING_STARTED.md) to go to the v8 branch_ -# Contributing to Umbraco CMS - -👍🎉 First off, thanks for taking the time to contribute! 🎉👍 - -The following is a set of guidelines for contributing to Umbraco CMS. - -These are mostly guidelines, not rules. Use your best judgment, 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 💖. - -## Contributing code changes - -This document gives you a quick overview on how to get started, we will link to in-depth documentation throughout if you need some more background info. - - -## 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. - -We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes. - -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. - -## How do I begin? - -Great question! The short version goes like this: - - * **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS) - - ![Fork the repository](img/forkrepository.png) - - * **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool - - ![Clone the fork](img/clonefork.png) - - * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md) - * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions) - * **Commit** - done? Yay! 🎉 It is recommended to 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` - * **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 a pull request](img/createpullrequest.png) - -### Further reading - -At this point you might want to [read on about contributing in depth](CONTRIBUTING_DETAILED.md). - -### Reviews - -You've sent us your first contribution, congratulations! Now what? - -The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request you to 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 - -## Styleguides - -To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. - -That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. - -## The 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 - -- [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. - -## 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: - -- 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 - -## 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). - - -## Contributing to Umbraco, in depth - -There are other ways to contribute, and there's a few more things that you might be wondering about. We will answer the [most common questions and ways to contribute in our detailed documentation](CONTRIBUTING_DETAILED.md). - -## Credits - -This contribution guide borrows heavily from the excellent work on [the Atom contribution guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md). A big [#h5yr](http://h5yr.com/) to them! +# Contributing to Umbraco CMS + +👍🎉 First off, thanks for taking the time to contribute! 🎉👍 + +The following is a set of guidelines for contributing to Umbraco CMS. + +These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖. + +**Code of conduct** + +This project and everyone participating in it is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk). + +**Table of contents** + +[Contributing code changes](#contributing-code-changes) + * [Guidelines for contributions we welcome](#guidelines-for-contributions-we-welcome) + * [What can I start with?](#what-can-i-start-with) + * [How do I begin?](#how-do-i-begin) + * [Pull requests](#pull-requests) + +[Reviews](#reviews) + * [Styleguides](#styleguides) + * [The PR team](#the-pr-team) + * [Questions?](#questions) + +[Working with the code](#working-with-the-code) + * [Building Umbraco from source code](#building-umbraco-from-source-code) + * [Working with the source code](#working-with-the-source-code) + * [Making changes after the PR was opened](#making-changes-after-the-pr-was-opened) + * [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions) + * [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository) + +## Contributing code changes + +This document gives you a quick overview on how to get started. + +### Guidelines for contributions we welcome + +Not all changes are wanted, so on 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. + +We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes. + +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. + +### What can I start with? + +Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+) + +### How do I begin? + +Great question! The short version goes like this: + + * **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS) + + ![Fork the repository](img/forkrepository.png) + + * **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool + + ![Clone the fork](img/clonefork.png) + + * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md) + * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions) + * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/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 a pull request](img/createpullrequest.png) + +### Pull requests +The most successful pull requests usually look a like this: + + * Fill in the required template + * Include screenshots and animated GIFs in your pull request whenever possible. + * Unit tests, while optional are awesome, thank you! + * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated + +Again, these are guidelines, not strict requirements. + +## Reviews + +You've sent us your first contribution, congratulations! Now what? + +The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request you to 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 + +### Styleguides + +To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. + +That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. + +### The 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 + +- [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. + +### 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: + +- 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 + +## Working with the code + +### Building Umbraco from source code + +In order to build the Umbraco source code locally, first make sure you have the following installed. + + * Visual Studio 2017 v15.9.7+ + * 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. + +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. + +![Gulp build in Visual Studio](img/gulpbuild.png) + +After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser, follow the directions there to get a working Umbraco install up and running. + +### Working with the source code + +Some parts of our source code are over 10 years old now. And when we say "old", we mean "mature" of course! + +There's two big areas that you should know about: + + 1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes. + You may need to run the following commands to set up gulp properly: + ``` + npm cache clean --force + npm install + npm run build + ``` + 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. + + * [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. + +![Which branch should I target?](img/defaultbranch.png) + +### Making changes after the PR was opened + +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. + +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. + +To sync your fork with this original one, you'll have to add the upstream url, you only have to do this once: + +``` +git remote add upstream https://github.com/umbraco/Umbraco-CMS.git +``` + +Then when you want to get the changes from the main repository: + +``` +git fetch upstream +git rebase upstream/v8/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)) diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md deleted file mode 100644 index 422dd1f664..0000000000 --- a/.github/CONTRIBUTING_DETAILED.md +++ /dev/null @@ -1,162 +0,0 @@ -# Contributing in detail - -There's more than one way to contribute to Umbraco, there's some more suggestions below. - -When contributing code to Umbraco there's plenty of things you'll want to know, skip down to [What should I know before I get started](#what-should-i-know-before-i-get-started) for the answers to your burning questions. - -#### Table Of Contents - -[How Can I Contribute?](#how-can-i-contribute) - * [Reporting Bugs](#reporting-bugs) - * [Suggesting Enhancements](#suggesting-enhancements) - * [Your First Code Contribution](#your-first-code-contribution) - * [Pull Requests](#pull-requests) - -[Styleguides](#styleguides) - -[What should I know before I get started?](#what-should-i-know-before-i-get-started) - * [Working with the source code](#working-with-the-source-code) - * [What branch should I target for my contributions?](#what-branch-should-i-target-for-my-contributions) - * [Building Umbraco from source code](#building-umbraco-from-source-code) - * [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository) - -## How Can I Contribute? - -### Reporting Bugs -This section guides you through submitting a bug report for Umbraco CMS. Following these guidelines helps maintainers and the community understand your report 📝, reproduce the behavior 💻 💻, and find related reports 🔎. - -Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/umbraco/Umbraco-CMS/issues/new/choose), the information it asks for helps us resolve issues faster. - -> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. - -##### Before Submitting A Bug Report - - * Most importantly, check **if you can reproduce the problem** in the [latest version of Umbraco](https://our.umbraco.com/download/). We might have already fixed your particular problem. - * It also helps tremendously to check if the issue you're experiencing is present in **a clean install** of the Umbraco version you're currently using. Custom code can have side-effects that don't occur in a clean install. - * **Use the Google**. Whatever you're experiencing, Google it plus "Umbraco" - usually you can get some pretty good hints from the search results, including open issues and further troubleshooting hints. - * If you do find and existing issue has **and the issue is still open**, add a comment to the existing issue if you have additional information. If you have the same problem and no new info to add, just "star" the issue. - -Explain the problem and include additional details to help maintainers reproduce the problem. The following is a long description which we've boiled down into a few very simple questions in the issue tracker when you create a new issue. We're listing the following hints to indicate that the most successful reports usually have a lot of this ground covered: - - * **Use a clear and descriptive title** for the issue to identify the problem. - * **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining which steps you took in the backoffice to get to a certain undesireable result, e.g. you created a document type, inherting 3 levels deep, added a certain datatype, tried to save it and you got an error. - * **Provide specific examples to demonstrate the steps**. If you wrote some code, try to provide a code sample as specific as possible to be able to reproduce the behavior. - * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. - * **Explain which behavior you expected to see instead and why.** - -Provide more context by answering these questions: - - * **Can you reproduce the problem** when `debug="false"` in your `web.config` file? - * **Did the problem start happening recently** (e.g. after updating to a new version of Umbraco) or was this always a problem? - * **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. - -Include details about your configuration and environment: - - * **Which version of Umbraco are you using?** - * **What is the environment you're using Umbraco in?** Is this a problem on your local machine or on a server. Tell us about your configuration: Windows version, IIS/IISExpress, database type, etc. - * **Which packages do you have installed?** - -### Suggesting Enhancements - -This section guides you through submitting an enhancement suggestion for Umbraco, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion 📝 and find related suggestions 🔎. - -Most of the suggestions in the [reporting bugs](#reporting-bugs) section also count for suggesting enhancements. - -Some additional hints that may be helpful: - - * **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Umbraco which the suggestion is related to. - * **Explain why this enhancement would be useful to most Umbraco users** and isn't something that can or should be implemented as a [community package](https://our.umbraco.com/projects/). - -### Your First Code Contribution - -Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](https://issues.umbraco.org/issues?q=&project=U4&tagValue=upforgrabs&release=&issueType=&search=search) or on the [new issue tracker](https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aopen+is%3Aissue+label%3Acommunity%2Fup-for-grabs). - -### Pull Requests - -The most successful pull requests usually look a like this: - - * Fill in the required template - * Include screenshots and animated GIFs in your pull request whenever possible. - * Unit tests, while optional are awesome, thank you! - * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated - -Again, these are guidelines, not strict requirements. - -## Making changes after the PR was opened - -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! - -## Styleguides - -To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. - -That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. - -## What should I know before I get started? - -### Working with the source code - -Some parts of our source code is 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: - - 1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes. - You may need to run the following commands to set up gulp properly: - ``` - npm cache clean - npm install -g gulp - npm install -g gulp-cli - npm install - gulp build - ``` - 2. "The rest" is a C# based codebase, with some traces of our WebForms past but mostly ASP.NET MVC based these days. 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. - - * [The AngularJS based backoffice files](https://our.umbraco.com/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`) - * [The rest](https://our.umbraco.com/apidocs/csharp/) - -### What 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 `dev-v7`. Whatever the default is, that's where we'd like you to target your contributions. - -![What branch do you want me to target?](img/defaultbranch.png) - -### Building Umbraco from source code - -In order to build the Umbraco source code locally, first make sure you have the following installed. - - * Visual Studio 2017 v15.3+ - * Node v10+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v10) - * npm v6.4.1+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v6.4.1) - -The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details. - -Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 (version 15.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile. - -![Gulp build in Visual Studio](img/gulpbuild.png) - -After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser, follow the directions there to get a working Umbraco install up and running. - -### 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. - -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. - -To sync your fork with this original one, you'll have to add the upstream url, you only have to do this once: - -``` -git remote add upstream https://github.com/umbraco/Umbraco-CMS.git -``` - -Then when you want to get the changes from the main repository: - -``` -git fetch upstream -git rebase upstream/dev-v7 -``` - -In this command we're syncing with the `dev-v7` branch, but you can of course choose another one if needed. - -(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated)) diff --git a/.github/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md deleted file mode 100644 index b94feb0b6b..0000000000 --- a/.github/CONTRIBUTION_GUIDELINES.md +++ /dev/null @@ -1,33 +0,0 @@ -# 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. These issues are usually small enough to fit in the "Small PRs" category and 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’ll get feedback to you 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. - -### Pull request or package? - -If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and to fix bugs. - -Eventually, a package could "graduate" to be included in the CMS. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8cb9017518..bc0a72539c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,11 +2,20 @@ - [ ] I have added steps to test this contribution in the description below -If there's an existing issue for this PR then this fixes: +If there's an existing issue for this PR then this fixes ### Description - + diff --git a/.github/README.md b/.github/README.md index effec2fb68..bdf9ef9f67 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,64 +1,40 @@ -[![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=dev-v8)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge) - - -_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._ - -_Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._ - -_Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md)._ - -When is Umbraco 8 coming? -========================= -When it's ready. We're done with the major parts of the architecture work and are focusing on three separate tracks to prepare Umbraco 8 for release: -1) Editor Track (_currently in progress_). Without editors, there's no market for Umbraco. So we want to make sure that Umbraco 8 is full of love for editors. -2) Partner Track. Without anyone implementing Umbraco, there's nothing for editors to update. So we want to make sure that Umbraco 8 is a joy to implement -3) Contributor Track. Without our fabulous ecosystem of both individual Umbracians and 3rd party ISVs, Umbraco wouldn't be as rich a platform as it is today. We want to make sure that it's easy, straight forward and as backwards-compatible as possible to create packages for Umbraco - -Once a track is done, we start releasing previews where we ask people to test the features we believe are ready. While the testing is going on and we gather feedback, we move on to the next track. This doesn't mean that there hasn't already been work done in the area, but that a track focuses on finalizing, polishing and preparing the features for release. - -Umbraco CMS -=========== -The friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com) - -[![ScreenShot](img/vimeo.png)](https://vimeo.com/172382998/) - -## Umbraco CMS -Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social. - -## Watch an introduction video - -[![ScreenShot](https://shop.umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/) - -## Umbraco - The Friendly CMS - -For the first time on the Microsoft platform, there is a free user- and developer-friendly CMS that makes it quick and easy to create websites - and a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, right out of the box. - -Umbraco is not only loved by developers, but is a content editor's dream. Enjoy intuitive editing tools, media management, responsive views, and approval workflows to send your content live. - -Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scalable. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay. - -To view more examples, please visit [https://umbraco.com/case-studies-testimonials/](https://umbraco.com/case-studies-testimonials/) - -## Why Open Source? -As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible, and community contributions and packages are available for all to use. - -## Trying out Umbraco CMS - -[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. - -## 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. - -## Contribute to Umbraco - -Umbraco is contribution-focused and community-driven. If you want to contribute back to Umbraco, please check out our [guide to contributing](CONTRIBUTING.md). - -## Found a bug? - -Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](CONTRIBUTING_DETAILED.md#reporting-bugs). - -You can comment and report issues on the [github issue tracker](https://github.com/umbraco/Umbraco-CMS/issues). -Since [September 2018](https://umbraco.com/blog/a-second-take-on-umbraco-issue-tracker-hello-github-issues/), the old issue tracker is in read-only mode, but can still be found at [http://issues.umbraco.org](http://issues.umbraco.org). +# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/dev)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge) + +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. + +Learn more at [umbraco.com](https://umbraco.com) + +

+ Umbraco Logo +

+ +See the official [Umbraco website](https://umbraco.com) for an introduction, core mission and values of the product and team behind it. + +- [Getting Started](#getting-started) +- [Documentation](#documentation) +- [Community](#join-the-umbraco-community) +- [Contributing](#contributing) + +Please also see our [Code of Conduct](CODE_OF_CONDUCT.md). + +## Getting Started + +[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. + +## Documentation + +The documentation for Umbraco CMS can be found [on Our Umbraco](https://our.umbraco.com/documentation/). The source for the Umbraco docs is [open source as well](https://github.com/umbraco/UmbracoDocs) and we're happy to look at your documentation contributions. + +## 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. + +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) + + +## Contributing + +Umbraco is contribution-focused and community-driven. If you want to contribute back to the Umbraco source code, please check out our [guide to contributing](CONTRIBUTING.md). + diff --git a/.github/REVIEW_PROCESS.md b/.github/REVIEW_PROCESS.md deleted file mode 100644 index 917d25b090..0000000000 --- a/.github/REVIEW_PROCESS.md +++ /dev/null @@ -1,25 +0,0 @@ -# Review process - -You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response. - -In order to set some expectations, here's what happens next. - -## Review process - -You will get an initial reply within 48 hours (workdays) to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. - -You will get feedback within at most 14 days after opening the PR. You’ll most likely get feedback sooner though. Then there are a few possible outcomes: - -- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco -- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible -- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see -- Your proposed change is awesome but.. not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!) - -## Are you still available? - -We understand you have other things to do and can't just drop everything to help us out. -So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time. - -If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it. - -There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository. \ No newline at end of file diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md deleted file mode 100644 index 49a898cfe1..0000000000 --- a/.github/V8_GETTING_STARTED.md +++ /dev/null @@ -1,37 +0,0 @@ -## A quick start guide for getting up and runnning with Umbraco v8 - -### What you need: - -* [Visual Studio 2017 Community (Free)](https://www.visualstudio.com/vs/community/), or Professional, Enterprise, etc... _(**Minimum of Version 15.7** or higher, this is important, you WILL get issues with lesser versions)_ -* .NET Framework 4.7.2 installed, get it here: https://www.microsoft.com/net/download/thank-you/net472?survey=false -* .NET Framework 4.7.2 developer pack, get it here: https://www.microsoft.com/net/download/thank-you/net472-developer-pack _(be sure this is the ENU file which will be named `NDP472-DevPack-ENU.exe`)_ -* Clone the Umbraco repository using the `dev-v8` branch. If your git client doesn't support specifying the branch as you clone then use the command `git clone --single-branch -b dev-v8 `. _(If you clone the repo using the default v7 branch and then checkout the `dev-v8` branch you **might** get problems)_ - -### Start the solution - -* Open the `/src/umbraco.sln` Visual Studio solution -* Start the solution (easiest way is to use `ctrl + F5`) -* When the solution is first built this may take some time since it will restore all nuget and npm packages, build the .net solution and also build the angular solution -* When the website starts you'll see the Umbraco installer and just follow the prompts -* You're all set! - -### Want to run from a zip instead? - -If you just want to try out a few things, you can run the site from a zip file which you can download from here https://github.com/umbraco/Umbraco-CMS/releases/tag/temp8-cg18. - -We recommend running the site with the Visual Studio since you'll be able to remain up to date with the latest source code changes. - -### Making code changes - -* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md)_ -* Any .NET changes you make you just need to compile -* Any Angular/JS changes you make you will need to make sure you are running the Gulp build. Easiest way to do this is from within Visual Studio in the `Task Runner Explorer`. You can find this window by pressing `ctrl + q` and typing in `Task Runner Explorer`. In this window you'll see all Gulp tasks, double click on the `dev` task, this will compile the angular solution and start a file watcher, then any html/js changes you make are automatically built. -* When making js changes, you should have the chrome developer tools open to ensure that cache is disabled -* If you are only making C# changes and are not touching the UI code at all, you can significantly speed up the VS build by adding an empty file specifically called `~/src/preserve.belle`. The UI (Belle) build will then be skipped during a Rebuild. - -### What to work on? - -We are keeping track of [known issues and limitations here](http://issues.umbraco.org/issue/U4-11279). These line items will eventually be turned into actual tasks to be worked on. Feel free to help us keep this list updated if you find issues and even help fix some of these items. If there is a particular item you'd like to help fix please mention this on the task and we'll create a sub task for the item to continue discussion there. - -There's [a list of tasks for v8 that haven't been completed](https://github.com/umbraco/Umbraco-CMS/labels/release%2F8.0.0). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done. - diff --git a/.github/img/defaultbranch.png b/.github/img/defaultbranch.png index 0595e0b4ea..f3a5b9efbc 100644 Binary files a/.github/img/defaultbranch.png and b/.github/img/defaultbranch.png differ diff --git a/.github/img/logo.png b/.github/img/logo.png new file mode 100644 index 0000000000..b761707588 Binary files /dev/null and b/.github/img/logo.png differ diff --git a/.github/img/vimeo.png b/.github/img/vimeo.png deleted file mode 100644 index d3c12465ba..0000000000 Binary files a/.github/img/vimeo.png and /dev/null differ diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 88281cb84b..426df0c341 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms.Core 8.0.0 Umbraco Cms Core Binaries @@ -22,7 +22,6 @@ the latter would pick anything below 3.0.0 and that includes prereleases such as 3.0.0-alpha, and we do not want this to happen as the alpha of the next major is, really, the next major already. --> - diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 4ef825f931..e81c199288 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms.Web 8.0.0 Umbraco Cms Core Binaries diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 6f4bcd5526..c93a82c691 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms 8.0.0 Umbraco Cms @@ -25,7 +25,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + diff --git a/build/NuSpecs/tools/Readme.txt b/build/NuSpecs/tools/Readme.txt index 53915436ac..e40b0dbc7e 100644 --- a/build/NuSpecs/tools/Readme.txt +++ b/build/NuSpecs/tools/Readme.txt @@ -1,12 +1,13 @@ - _ _ __ __ ____ _____ _____ ____ - | | | | \/ | _ \| __ \ /\ / ____/ __ \ - | | | | \ / | |_) | |__) | / \ | | | | | | - | | | | |\/| | _ <| _ / / /\ \| | | | | | - | |__| | | | | |_) | | \ \ / ____ | |___| |__| | - \____/|_| |_|____/|_| \_/_/ \_\_____\____/ + 888 + 888 +888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b. +888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b +888 888 888 888 888 888 888 888 .d888888 888 888 888 +Y88b 888 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P + "Y88888 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P" ----------------------------------------------------- +------------------------------------------------------------------ Don't forget to build! diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 2da3206e7a..14778e0f10 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -126,45 +126,55 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 deleted file mode 100644 index 9b609a498e..0000000000 --- a/build/NuSpecs/tools/install.core.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -Write-Host "installPath:" "${installPath}" -Write-Host "toolsPath:" "${toolsPath}" - -Write-Host " " - -if ($project) { - $dateTime = Get-Date -Format yyyyMMdd-HHmmss - - # Create paths and list them - $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName - Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" - Write-Host "backupPath:" "${backupPath}" - $copyLogsPath = Join-Path $backupPath "CopyLogs" - Write-Host "copyLogsPath:" "${copyLogsPath}" - $umbracoBinFolder = Join-Path $projectPath "bin" - Write-Host "umbracoBinFolder:" "${umbracoBinFolder}" - - # Create backup folder and logs folder if it doesn't exist yet - New-Item -ItemType Directory -Force -Path $backupPath - New-Item -ItemType Directory -Force -Path $copyLogsPath - - # After backing up, remove all umbraco dlls from bin folder in case dll files are included in the VS project - # See: http://issues.umbraco.org/issue/U4-4930 - - if(Test-Path $umbracoBinFolder) { - $umbracoBinBackupPath = Join-Path $backupPath "bin" - - New-Item -ItemType Directory -Force -Path $umbracoBinBackupPath - - robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log - - # Delete files Umbraco ships with - if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Web.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Examine.dll) { Remove-Item $umbracoBinFolder\Umbraco.Examine.dll -Force -Confirm:$false } - - # Delete files Umbraco depends upon - $amd64Folder = Join-Path $umbracoBinFolder "amd64" - if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false } - $x86Folder = Join-Path $umbracoBinFolder "x86" - if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false } - if(Test-Path $umbracoBinFolder\AutoMapper.dll) { Remove-Item $umbracoBinFolder\AutoMapper.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\AutoMapper.Net4.dll) { Remove-Item $umbracoBinFolder\AutoMapper.Net4.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ClientDependency.Core.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ClientDependency.Core.Mvc.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.Mvc.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\CookComputing.XmlRpcV2.dll) { Remove-Item $umbracoBinFolder\CookComputing.XmlRpcV2.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Examine.dll) { Remove-Item $umbracoBinFolder\Examine.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\HtmlAgilityPack.dll) { Remove-Item $umbracoBinFolder\HtmlAgilityPack.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ICSharpCode.SharpZipLib.dll) { Remove-Item $umbracoBinFolder\ICSharpCode.SharpZipLib.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Infrastructure.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Infrastructure.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Helpers.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Owin.dll) { Remove-Item $umbracoBinFolder\Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Semver.dll) { Remove-Item $umbracoBinFolder\Semver.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Collections.Immutable.dll) { Remove-Item $umbracoBinFolder\System.Collections.Immutable.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Reflection.Metadata.dll) { Remove-Item $umbracoBinFolder\System.Reflection.Metadata.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Net.Http.Extensions.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Extensions.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Net.Http.Formatting.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Formatting.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Net.Http.Primitives.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Primitives.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\System.Web.Helpers.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Http.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Http.WebHost.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.WebHost.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Mvc.dll) { Remove-Item $umbracoBinFolder\System.Web.Mvc.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.Razor.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.Deployment.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Deployment.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Razor.dll -Force -Confirm:$false } - } -} diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 5ca42b54a1..1411cb5c97 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -11,37 +11,15 @@ if ($project) { # Create paths and list them $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" - Write-Host "backupPath:" "${backupPath}" - $copyLogsPath = Join-Path $backupPath "CopyLogs" - Write-Host "copyLogsPath:" "${copyLogsPath}" $webConfigSource = Join-Path $projectPath "Web.config" Write-Host "webConfigSource:" "${webConfigSource}" $configFolder = Join-Path $projectPath "Config" Write-Host "configFolder:" "${configFolder}" - # Create backup folder and logs folder if it doesn't exist yet - New-Item -ItemType Directory -Force -Path $backupPath - New-Item -ItemType Directory -Force -Path $copyLogsPath - - # Create a backup of original web.config - Copy-Item $webConfigSource $backupPath -Force - - # Backup config files folder - if(Test-Path $configFolder) { - $umbracoBackupPath = Join-Path $backupPath "Config" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - - robocopy $configFolder $umbracoBackupPath /e /LOG:$copyLogsPath\ConfigBackup.log - } - # Copy umbraco and umbraco_files from package to project folder $umbracoFolder = Join-Path $projectPath "Umbraco" New-Item -ItemType Directory -Force -Path $umbracoFolder $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco" - $umbracoBackupPath = Join-Path $backupPath "Umbraco" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log $copyWebconfig = $true @@ -100,11 +78,6 @@ if ($project) { } } - $installFolder = Join-Path $projectPath "Install" - if(Test-Path $installFolder) { - Remove-Item $installFolder -Force -Recurse -Confirm:$false - } - # Open appropriate readme if($copyWebconfig -eq $true) { diff --git a/build/build.ps1 b/build/build.ps1 index d811c2cc21..c186d9adaa 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -15,7 +15,12 @@ [Parameter(Mandatory=$false)] [Alias("c")] [Alias("cont")] - [switch] $continue = $false + [switch] $continue = $false, + + # execute a command + [Parameter(Mandatory=$false, ValueFromRemainingArguments=$true)] + [String[]] + $command ) # ################################################################ @@ -475,7 +480,11 @@ # run if (-not $get) { - $ubuild.Build() + if ($command.Length -eq 0) + { + $command = @( "Build" ) + } + $ubuild.RunMethod($command); if ($ubuild.OnError()) { return } } if ($get) { return $ubuild } diff --git a/src/Umbraco.Core/BindingRedirects.cs b/src/Umbraco.Core/BindingRedirects.cs index 17e187b7ae..e698ffa646 100644 --- a/src/Umbraco.Core/BindingRedirects.cs +++ b/src/Umbraco.Core/BindingRedirects.cs @@ -1,10 +1,10 @@ using System; using System.Reflection; -using System.Text.RegularExpressions; using System.Web; using Umbraco.Core; -[assembly: PreApplicationStartMethod(typeof(BindingRedirects), "Initialize")] +// no binding redirect for now = de-activate +//[assembly: PreApplicationStartMethod(typeof(BindingRedirects), "Initialize")] namespace Umbraco.Core { @@ -18,7 +18,7 @@ namespace Umbraco.Core // this only gets called when an assembly can't be resolved AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; } - + /// /// This is used to do an assembly binding redirect via code - normally required due to signature changes in assemblies /// @@ -27,14 +27,19 @@ namespace Umbraco.Core /// private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { + // When an assembly can't be resolved. In here we can do magic with the assembly name and try loading another. + + // keep here for reference - we don't use AutoMapper + /* //AutoMapper: + // this is used for loading a signed assembly of AutoMapper (v. 3.1+) without having to recompile old code. // ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stackoverflow if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null")) return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005")); + */ return null; - } } } diff --git a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs index b1a12ec411..fecab66d84 100644 --- a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { @@ -13,11 +12,6 @@ namespace Umbraco.Core.Cache public static NoCacheRepositoryCachePolicy Instance { get; } = new NoCacheRepositoryCachePolicy(); - public IRepositoryCachePolicy Scoped(IAppPolicyCache runtimeCache, IScope scope) - { - throw new NotImplementedException(); - } - public TEntity Get(TId id, Func performGet, Func> performGetAll) { return performGet(id); diff --git a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs index 501907a6e7..8371f9b279 100644 --- a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Compose private static void ContentService_Moved(IContentService sender, MoveEventArgs e) { - foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContent.ToInvariantString()))) + foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContentString))) { var relationService = Current.Services.RelationService; const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; @@ -37,7 +37,7 @@ namespace Umbraco.Core.Compose private static void MediaService_Moved(IMediaService sender, MoveEventArgs e) { - foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMedia.ToInvariantString()))) + foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMediaString))) { var relationService = Current.Services.RelationService; const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; diff --git a/src/Umbraco.Core/Composing/Composition.cs b/src/Umbraco.Core/Composing/Composition.cs index d7686ccd07..34c5296dce 100644 --- a/src/Umbraco.Core/Composing/Composition.cs +++ b/src/Umbraco.Core/Composing/Composition.cs @@ -133,7 +133,11 @@ namespace Umbraco.Core.Composing Configs.RegisterWith(_register); - return _register.CreateFactory(); + IFactory factory = null; + // ReSharper disable once AccessToModifiedClosure -- on purpose + _register.Register(_ => factory, Lifetime.Singleton); + factory = _register.CreateFactory(); + return factory; } /// diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs index ed875ec973..165fa10be7 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using Umbraco.Core.Mapping; using Umbraco.Core.Models.Identity; namespace Umbraco.Core.Composing.CompositionExtensions @@ -8,7 +8,9 @@ namespace Umbraco.Core.Composing.CompositionExtensions { public static Composition ComposeCoreMappingProfiles(this Composition composition) { - composition.Register(); + composition.RegisterUnique(); + composition.WithCollectionBuilder() + .Add(); return composition; } } diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs index 03f13f96cb..23dc9e67c6 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs @@ -46,6 +46,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); return composition; } diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs index ead2524d80..0baefe104b 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs @@ -98,7 +98,6 @@ namespace Umbraco.Core.Composing.CompositionExtensions : appPlugins.GetDirectories() .SelectMany(x => x.GetDirectories("Lang")) .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); //user defined langs that overwrite the default, these should not be used by plugin creators @@ -106,7 +105,6 @@ namespace Umbraco.Core.Composing.CompositionExtensions ? Enumerable.Empty() : configLangFolder .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); return new LocalizedTextServiceFileSources( diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 1b16ad2b8e..f12bf0dd63 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PackageActions; using Umbraco.Core.Packaging; @@ -103,6 +104,9 @@ namespace Umbraco.Core.Composing #region Getters + public static UmbracoMapper Mapper + => _factory.GetInstance(); + public static IShortStringHelper ShortStringHelper => _shortStringHelper ?? (_shortStringHelper = _factory?.TryGetInstance() ?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(Configs.Settings()))); diff --git a/src/Umbraco.Core/Composing/IComposer.cs b/src/Umbraco.Core/Composing/IComposer.cs index a7a159d697..b73a745b61 100644 --- a/src/Umbraco.Core/Composing/IComposer.cs +++ b/src/Umbraco.Core/Composing/IComposer.cs @@ -8,7 +8,6 @@ /// /// Compose. /// - /// void Compose(Composition composition); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/ICoreComposer.cs b/src/Umbraco.Core/Composing/ICoreComposer.cs index ff09f41f6b..1e9e5fced5 100644 --- a/src/Umbraco.Core/Composing/ICoreComposer.cs +++ b/src/Umbraco.Core/Composing/ICoreComposer.cs @@ -4,10 +4,8 @@ /// Represents a core . /// /// - /// All core composers are required by (compose before) all user composers, - /// and require (compose after) all runtime composers. + /// Core composers compose after the initial composer, and before user composers. /// - [ComposeAfter(typeof(IRuntimeComposer))] public interface ICoreComposer : IComposer { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/IRuntimeComposer.cs b/src/Umbraco.Core/Composing/IRuntimeComposer.cs deleted file mode 100644 index 2a352dc783..0000000000 --- a/src/Umbraco.Core/Composing/IRuntimeComposer.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Core.Composing -{ - /// - /// Represents a runtime . - /// - /// - /// All runtime composers are required by (compose before) all core composers - /// - public interface IRuntimeComposer : IComposer - { } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Composing/IUserComposer.cs b/src/Umbraco.Core/Composing/IUserComposer.cs index 136e69a469..96f6b38189 100644 --- a/src/Umbraco.Core/Composing/IUserComposer.cs +++ b/src/Umbraco.Core/Composing/IUserComposer.cs @@ -4,9 +4,9 @@ /// Represents a user . /// /// - /// All user composers require (compose after) all core composers. + /// User composers compose after core composers, and before the final composer. /// [ComposeAfter(typeof(ICoreComposer))] public interface IUserComposer : IComposer { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs new file mode 100644 index 0000000000..9261423bbc --- /dev/null +++ b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Composing +{ + /// + /// Implements an un-ordered collection builder. + /// + /// The type of the builder. + /// The type of the collection. + /// The type of the items. + /// + /// A set collection builder is the most basic collection builder, + /// where items are not ordered. + /// + public abstract class SetCollectionBuilderBase : CollectionBuilderBase + where TBuilder : SetCollectionBuilderBase + where TCollection : class, IBuilderCollection + { + protected abstract TBuilder This { get; } + + /// + /// Clears all types in the collection. + /// + /// The builder. + public TBuilder Clear() + { + Configure(types => types.Clear()); + return This; + } + + /// + /// Adds a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Add() + where T : TItem + { + Configure(types => + { + var type = typeof(T); + if (types.Contains(type)) types.Remove(type); + types.Add(type); + }); + return This; + } + + /// + /// Adds a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Add(Type type) + { + Configure(types => + { + EnsureType(type, "register"); + if (types.Contains(type)) types.Remove(type); + types.Add(type); + }); + return This; + } + + /// + /// Adds types to the collections. + /// + /// The types to append. + /// The builder. + public TBuilder Add(IEnumerable types) + { + Configure(list => + { + foreach (var type in types) + { + // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast + EnsureType(type, "register"); + if (list.Contains(type)) list.Remove(type); + list.Add(type); + } + }); + return This; + } + + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove() + where T : TItem + { + Configure(types => + { + var type = typeof(T); + if (types.Contains(type)) types.Remove(type); + }); + return This; + } + + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove(Type type) + { + Configure(types => + { + EnsureType(type, "remove"); + if (types.Contains(type)) types.Remove(type); + }); + return This; + } + + /// + /// Replaces a type in the collection. + /// + /// The type to replace. + /// The type to insert. + /// The builder. + /// Throws if the type to replace does not already belong to the collection. + public TBuilder Replace() + where TReplaced : TItem + where T : TItem + { + Configure(types => + { + var typeReplaced = typeof(TReplaced); + var type = typeof(T); + if (typeReplaced == type) return; + + var index = types.IndexOf(typeReplaced); + if (index < 0) throw new InvalidOperationException(); + + if (types.Contains(type)) types.Remove(type); + index = types.IndexOf(typeReplaced); // in case removing type changed index + types.Insert(index, type); + types.Remove(typeReplaced); + }); + return This; + } + + /// + /// Replaces a type in the collection. + /// + /// The type to replace. + /// The type to insert. + /// The builder. + /// Throws if the type to replace does not already belong to the collection. + public TBuilder Replace(Type typeReplaced, Type type) + { + Configure(types => + { + EnsureType(typeReplaced, "find"); + EnsureType(type, "register"); + + if (typeReplaced == type) return; + + var index = types.IndexOf(typeReplaced); + if (index < 0) throw new InvalidOperationException(); + + if (types.Contains(type)) types.Remove(type); + index = types.IndexOf(typeReplaced); // in case removing type changed index + types.Insert(index, type); + types.Remove(typeReplaced); + }); + return This; + } + } +} diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs new file mode 100644 index 0000000000..35f85c9c2f --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a base class for collections of types. + /// + public abstract class TypeCollectionBuilderBase : ICollectionBuilder + where TCollection : class, IBuilderCollection + { + private readonly HashSet _types = new HashSet(); + + private Type Validate(Type type, string action) + { + if (!typeof(TConstraint).IsAssignableFrom(type)) + throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}."); + return type; + } + + public void Add(Type type) => _types.Add(Validate(type, "add")); + + public void Add() => Add(typeof(T)); + + public void Add(IEnumerable types) + { + foreach (var type in types) Add(type); + } + + public void Remove(Type type) => _types.Remove(Validate(type, "remove")); + + public void Remove() => Remove(typeof(T)); + + public TCollection CreateCollection(IFactory factory) + { + return factory.CreateInstance(_types); + } + + public void RegisterWith(IRegister register) + { + register.Register(CreateCollection, Lifetime.Singleton); + } + } +} diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index e1c7ad4467..828a577c34 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -203,6 +203,28 @@ namespace Umbraco.Core composition.RegisterUnique(_ => registrar); } + /// + /// Sets the database server messenger options. + /// + /// The composition. + /// A function creating the options. + /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. + public static void SetDatabaseServerMessengerOptions(this Composition composition, Func factory) + { + composition.RegisterUnique(factory); + } + + /// + /// Sets the database server messenger options. + /// + /// The composition. + /// Options. + /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. + public static void SetDatabaseServerMessengerOptions(this Composition composition, DatabaseServerMessengerOptions options) + { + composition.RegisterUnique(_ => options); + } + /// /// Sets the short string helper. /// diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 09962fab5a..94deb28d87 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net.Configuration; using System.Web; using System.Web.Configuration; +using System.Web.Hosting; using System.Xml.Linq; using Umbraco.Core.IO; @@ -17,16 +18,15 @@ namespace Umbraco.Core.Configuration /// public class GlobalSettings : IGlobalSettings { + private string _localTempPath; - #region Private static fields - - + // TODO these should not be static private static string _reservedPaths; private static string _reservedUrls; + //ensure the built on (non-changeable) reserved paths are there at all times internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! internal const string StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma! - #endregion /// /// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config) @@ -131,7 +131,7 @@ namespace Umbraco.Core.Configuration : "~/App_Data/umbraco.config"; } } - + /// /// Gets the path to umbraco's root directory (/umbraco by default). /// @@ -163,7 +163,7 @@ namespace Umbraco.Core.Configuration SaveSetting(Constants.AppSettings.ConfigurationStatus, value); } } - + /// /// Saves a setting into the configuration file. /// @@ -206,7 +206,7 @@ namespace Umbraco.Core.Configuration ConfigurationManager.RefreshSection("appSettings"); } } - + /// /// Gets a value indicating whether umbraco is running in [debug mode]. /// @@ -250,7 +250,7 @@ namespace Umbraco.Core.Configuration } } } - + /// /// Returns the number of days that should take place between version checks. /// @@ -269,7 +269,7 @@ namespace Umbraco.Core.Configuration } } } - + /// public LocalTempStorage LocalTempStorageLocation { @@ -288,25 +288,43 @@ namespace Umbraco.Core.Configuration { get { + if (_localTempPath != null) + return _localTempPath; + switch (LocalTempStorageLocation) { case LocalTempStorage.AspNetTemp: - return System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData"); + return _localTempPath = System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData"); + case LocalTempStorage.EnvironmentTemp: - // include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back - // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not - // utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId - var appDomainHash = HttpRuntime.AppDomainAppId.GenerateHash(); - return System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); + + // environment temp is unique, we need a folder per site + + // use a hash + // combine site name and application id + // site name is a Guid on Cloud + // application id is eg /LM/W3SVC/123456/ROOT + // the combination is unique on one server + // and, if a site moves from worker A to B and then back to A... + // hopefully it gets a new Guid or new application id? + + var siteName = HostingEnvironment.SiteName; + var applicationId = HostingEnvironment.ApplicationID; // ie HttpRuntime.AppDomainAppId + + var hashString = siteName + "::" + applicationId; + var hash = hashString.GenerateHash(); + var siteTemp = System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", hash); + + return _localTempPath = System.IO.Path.Combine(siteTemp, "umbraco.config"); + //case LocalTempStorage.Default: //case LocalTempStorage.Unknown: default: - return IOHelper.MapPath("~/App_Data/TEMP"); + return _localTempPath = IOHelper.MapPath("~/App_Data/TEMP"); } } } - /// /// Gets the default UI language. /// diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index ac0cbde8de..509be46b61 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -93,7 +93,6 @@ namespace Umbraco.Core /// public const string DisableElectionForSingleServer = "Umbraco.Core.DisableElectionForSingleServer"; - /// /// Debug specific web.config AppSetting keys for Umbraco /// diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index b53a2b8eaf..cf4f80d87b 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -145,11 +145,11 @@ public const string PartialViewMacros = "partialViewMacros"; - public const string LogViewer = "logViewer"; + public const string LogViewer = "logViewer"; public static class Groups { - public const string Settings = "settingsGroup"; + public const string Settings = "settingsGroup"; public const string Templating = "templatingGroup"; diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index d49244d9f9..6c9407667a 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -92,10 +92,10 @@ namespace Umbraco.Core /// public const string Extension = "umbracoExtension"; - /// - /// The default height/width of an image file if the size can't be determined from the metadata - /// - public const int DefaultSize = 200; + /// + /// The default height/width of an image file if the size can't be determined from the metadata + /// + public const int DefaultSize = 200; } /// @@ -209,71 +209,71 @@ namespace Umbraco.Core public static Dictionary GetStandardPropertyTypeStubs() { return new Dictionary + { { + Comments, + new PropertyType(PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext, true, Comments) { - Comments, - new PropertyType(PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext, true, Comments) - { - Name = CommentsLabel - } - }, - { - FailedPasswordAttempts, - new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Integer, true, FailedPasswordAttempts) - { - Name = FailedPasswordAttemptsLabel - } - }, - { - IsApproved, - new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsApproved) - { - Name = IsApprovedLabel - } - }, - { - IsLockedOut, - new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsLockedOut) - { - Name = IsLockedOutLabel - } - }, - { - LastLockoutDate, - new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLockoutDate) - { - Name = LastLockoutDateLabel - } - }, - { - LastLoginDate, - new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLoginDate) - { - Name = LastLoginDateLabel - } - }, - { - LastPasswordChangeDate, - new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastPasswordChangeDate) - { - Name = LastPasswordChangeDateLabel - } - }, - { - PasswordAnswer, - new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordAnswer) - { - Name = PasswordAnswerLabel - } - }, - { - PasswordQuestion, - new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordQuestion) - { - Name = PasswordQuestionLabel - } + Name = CommentsLabel } - }; + }, + { + FailedPasswordAttempts, + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Integer, true, FailedPasswordAttempts) + { + Name = FailedPasswordAttemptsLabel + } + }, + { + IsApproved, + new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsApproved) + { + Name = IsApprovedLabel + } + }, + { + IsLockedOut, + new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsLockedOut) + { + Name = IsLockedOutLabel + } + }, + { + LastLockoutDate, + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLockoutDate) + { + Name = LastLockoutDateLabel + } + }, + { + LastLoginDate, + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLoginDate) + { + Name = LastLoginDateLabel + } + }, + { + LastPasswordChangeDate, + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastPasswordChangeDate) + { + Name = LastPasswordChangeDateLabel + } + }, + { + PasswordAnswer, + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordAnswer) + { + Name = PasswordAnswerLabel + } + }, + { + PasswordQuestion, + new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordQuestion) + { + Name = PasswordQuestionLabel + } + } + }; } } diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index f74b44b70b..d3e8b4ad3b 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -1,17 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { public static class Icons { - - /// /// System contenttype icon /// @@ -42,12 +34,10 @@ namespace Umbraco.Core /// public const string MemberType = "icon-users"; - /// /// System member icon /// public const string Template = "icon-layout"; - } } } diff --git a/src/Umbraco.Core/Constants-Indexes.cs b/src/Umbraco.Core/Constants-Indexes.cs index c73a170b62..1add0f721b 100644 --- a/src/Umbraco.Core/Constants-Indexes.cs +++ b/src/Umbraco.Core/Constants-Indexes.cs @@ -1,7 +1,4 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 4bf944e1e1..9a6a1a0632 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -123,7 +123,6 @@ namespace Umbraco.Core public static readonly Guid Template = new Guid(Strings.Template); public static readonly Guid ContentItem = new Guid(Strings.ContentItem); - } } } diff --git a/src/Umbraco.Core/Constants-PackageRepository.cs b/src/Umbraco.Core/Constants-PackageRepository.cs index 59005b3e2c..42cf61f982 100644 --- a/src/Umbraco.Core/Constants-PackageRepository.cs +++ b/src/Umbraco.Core/Constants-PackageRepository.cs @@ -3,7 +3,7 @@ public static partial class Constants { /// - /// Defines the constants used for the Umbraco package repository + /// Defines the constants used for the Umbraco package repository /// public static class PackageRepository { diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 32f9407616..0c2e246721 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -34,7 +34,6 @@ namespace Umbraco.Core /// public const string ContentPicker = "Umbraco.ContentPicker"; - /// /// DateTime. /// diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 0da5431e91..64216ba571 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -1,7 +1,4 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { @@ -22,7 +19,6 @@ namespace Umbraco.Core public const string PreviewCookieName = "UMB_PREVIEW"; public const string InstallerCookieName = "umb_installId"; - } } } diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index f802c2d0d9..a7d40b0b7d 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -7,6 +7,7 @@ using System.Web; using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NPoco.Expressions; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -52,8 +53,8 @@ namespace Umbraco.Core return ContentStatus.Unpublished; } - - + + #endregion /// @@ -134,9 +135,14 @@ namespace Umbraco.Core /// /// Sets the posted file value of a property. /// - /// This really is for FileUpload fields only, and should be obsoleted. For anything else, - /// you need to store the file by yourself using Store and then figure out - /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. + public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, HttpPostedFileBase postedFile, string culture = null, string segment = null) + { + content.SetValue(contentTypeBaseServiceProvider, propertyTypeAlias, postedFile.FileName, postedFile.InputStream, culture, segment); + } + + /// + /// Sets the posted file value of a property. + /// public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) { if (filename == null || filestream == null) return; diff --git a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs index ea2c8aa49f..4c0e8ed4f8 100644 --- a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Newtonsoft.Json; using Serilog.Events; using Serilog.Formatting.Compact.Reader; @@ -10,13 +11,15 @@ namespace Umbraco.Core.Logging.Viewer internal class JsonLogViewer : LogViewerSourceBase { private readonly string _logsPath; + private readonly ILogger _logger; - public JsonLogViewer(string logsPath = "", string searchPath = "") : base(searchPath) + public JsonLogViewer(ILogger logger, string logsPath = "", string searchPath = "") : base(searchPath) { if (string.IsNullOrEmpty(logsPath)) logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\"; _logsPath = logsPath; + _logger = logger; } private const int FileSizeCap = 100; @@ -77,8 +80,14 @@ namespace Umbraco.Core.Logging.Viewer using (var stream = new StreamReader(fs)) { var reader = new LogEventReader(stream); - while (reader.TryRead(out var evt)) + while (TryRead(reader, out var evt)) { + //We may get a null if log line is malformed + if (evt == null) + { + continue; + } + if (count > skip + take) { break; @@ -105,5 +114,21 @@ namespace Umbraco.Core.Logging.Viewer return logs; } + private bool TryRead(LogEventReader reader, out LogEvent evt) + { + try + { + return reader.TryRead(out evt); + } + catch (JsonReaderException ex) + { + // As we are reading/streaming one line at a time in the JSON file + // Thus we can not report the line number, as it will always be 1 + _logger.Error(ex, "Unable to parse a line in the JSON log file"); + + evt = null; + return true; + } + } } } diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs index 651e3de06e..8eb835b4d9 100644 --- a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs +++ b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Logging.Viewer { public void Compose(Composition composition) { - composition.SetLogViewer(_ => new JsonLogViewer()); + composition.SetLogViewer(_ => new JsonLogViewer(composition.Logger)); } } } diff --git a/src/Umbraco.Core/Mapping/IMapDefinition.cs b/src/Umbraco.Core/Mapping/IMapDefinition.cs new file mode 100644 index 0000000000..ffea07c3eb --- /dev/null +++ b/src/Umbraco.Core/Mapping/IMapDefinition.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Mapping +{ + /// + /// Defines maps for . + /// + public interface IMapDefinition + { + /// + /// Defines maps. + /// + void DefineMaps(UmbracoMapper mapper); + } +} diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs new file mode 100644 index 0000000000..e2438515f0 --- /dev/null +++ b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Mapping +{ + public class MapDefinitionCollection : BuilderCollectionBase + { + public MapDefinitionCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs new file mode 100644 index 0000000000..15d94e58a0 --- /dev/null +++ b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Mapping +{ + public class MapDefinitionCollectionBuilder : SetCollectionBuilderBase + { + protected override MapDefinitionCollectionBuilder This => this; + + protected override Lifetime CollectionLifetime => Lifetime.Transient; + } +} diff --git a/src/Umbraco.Core/Mapping/MapperContext.cs b/src/Umbraco.Core/Mapping/MapperContext.cs new file mode 100644 index 0000000000..a7044a05b9 --- /dev/null +++ b/src/Umbraco.Core/Mapping/MapperContext.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Mapping +{ + /// + /// Represents a mapper context. + /// + public class MapperContext + { + private readonly UmbracoMapper _mapper; + private IDictionary _items; + + /// + /// Initializes a new instance of the class. + /// + public MapperContext(UmbracoMapper mapper) + { + _mapper = mapper; + } + + /// + /// Gets a value indicating whether the context has items. + /// + public bool HasItems => _items != null; + + /// + /// Gets the context items. + /// + public IDictionary Items => _items ?? (_items = new Dictionary()); + + #region Map + + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// The target object. + public TTarget Map(object source) + => _mapper.Map(source, this); + + // let's say this is a bad (dangerous) idea, and leave it out for now + /* + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(object source, Action f) + { + f(this); + return _mapper.Map(source, this); + } + */ + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + public TTarget Map(TSource source) + => _mapper.Map(source, this); + + // let's say this is a bad (dangerous) idea, and leave it out for now + /* + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(TSource source, Action f) + { + f(this); + return _mapper.Map(source, this); + } + */ + + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// The target object. + public TTarget Map(TSource source, TTarget target) + => _mapper.Map(source, target, this); + + // let's say this is a bad (dangerous) idea, and leave it out for now + /* + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(TSource source, TTarget target, Action f) + { + f(this); + return _mapper.Map(source, target, this); + } + */ + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source) + { + return source.Select(Map).ToList(); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs new file mode 100644 index 0000000000..0831edab4e --- /dev/null +++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Mapping +{ + // notes: + // AutoMapper maps null to empty arrays, lists, etc + + // TODO: + // when mapping from TSource, and no map is found, consider the actual source.GetType()? + // when mapping to TTarget, and no map is found, consider the actual target.GetType()? + // not sure we want to add magic to this simple mapper class, though + + /// + /// Umbraco Mapper. + /// + /// + /// When a map is defined from TSource to TTarget, the mapper automatically knows how to map + /// from IEnumerable{TSource} to IEnumerable{TTarget} (using a List{TTarget}) and to TTarget[]. + /// When a map is defined from TSource to TTarget, the mapper automatically uses that map + /// for any source type that inherits from, or implements, TSource. + /// When a map is defined from TSource to TTarget, the mapper can map to TTarget exclusively + /// and cannot re-use that map for types that would inherit from, or implement, TTarget. + /// When using the Map{TSource, TTarget}(TSource source, ...) overloads, TSource is explicit. When + /// using the Map{TTarget}(object source, ...) TSource is defined as source.GetType(). + /// In both cases, TTarget is explicit and not typeof(target). + /// + public class UmbracoMapper + { + private readonly Dictionary>> _ctors + = new Dictionary>>(); + + private readonly Dictionary>> _maps + = new Dictionary>>(); + + /// + /// Initializes a new instance of the class. + /// + /// + public UmbracoMapper(MapDefinitionCollection profiles) + { + foreach (var profile in profiles) + profile.DefineMaps(this); + } + + #region Define + + private static TTarget ThrowCtor(TSource source, MapperContext context) + => throw new InvalidOperationException($"Don't know how to create {typeof(TTarget).FullName} instances."); + + private static void Identity(TSource source, TTarget target, MapperContext context) + { } + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + public void Define() + => Define(ThrowCtor, Identity); + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A mapping method. + public void Define(Action map) + => Define(ThrowCtor, map); + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A constructor method. + public void Define(Func ctor) + => Define(ctor, Identity); + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A constructor method. + /// A mapping method. + public void Define(Func ctor, Action map) + { + var sourceType = typeof(TSource); + var targetType = typeof(TTarget); + + var sourceCtors = DefineCtors(sourceType); + if (ctor != null) + sourceCtors[targetType] = (source, context) => ctor((TSource)source, context); + + var sourceMaps = DefineMaps(sourceType); + sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context); + } + + private Dictionary> DefineCtors(Type sourceType) + { + if (!_ctors.TryGetValue(sourceType, out var sourceCtor)) + sourceCtor = _ctors[sourceType] = new Dictionary>(); + return sourceCtor; + } + + private Dictionary> DefineMaps(Type sourceType) + { + if (!_maps.TryGetValue(sourceType, out var sourceMap)) + sourceMap = _maps[sourceType] = new Dictionary>(); + return sourceMap; + } + + #endregion + + #region Map + + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// The target object. + public TTarget Map(object source) + => Map(source, new MapperContext(this)); + + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(object source, Action f) + { + var context = new MapperContext(this); + f(context); + return Map(source, context); + } + + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context. + /// The target object. + public TTarget Map(object source, MapperContext context) + => Map(source, source?.GetType(), context); + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + public TTarget Map(TSource source) + => Map(source, new MapperContext(this)); + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(TSource source, Action f) + { + var context = new MapperContext(this); + f(context); + return Map(source, context); + } + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context. + /// The target object. + public TTarget Map(TSource source, MapperContext context) + => Map(source, typeof(TSource), context); + + private TTarget Map(object source, Type sourceType, MapperContext context) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + var targetType = typeof(TTarget); + + var ctor = GetCtor(sourceType, targetType); + var map = GetMap(sourceType, targetType); + + // if there is a direct constructor, map + if (ctor != null && map != null) + { + var target = ctor(source, context); + map(source, target, context); + return (TTarget)target; + } + + // otherwise, see if we can deal with enumerable + + var ienumerableOfT = typeof(IEnumerable<>); + + bool IsIEnumerableOfT(Type type) => + type.IsGenericType && + type.GenericTypeArguments.Length == 1 && + type.GetGenericTypeDefinition() == ienumerableOfT; + + // try to get source as an IEnumerable + var sourceIEnumerable = IsIEnumerableOfT(sourceType) ? sourceType : sourceType.GetInterfaces().FirstOrDefault(IsIEnumerableOfT); + + // if source is an IEnumerable and target is T[] or IEnumerable, we can create a map + if (sourceIEnumerable != null && IsEnumerableOrArrayOfType(targetType)) + { + var sourceGenericArg = sourceIEnumerable.GenericTypeArguments[0]; + var targetGenericArg = GetEnumerableOrArrayTypeArgument(targetType); + + ctor = GetCtor(sourceGenericArg, targetGenericArg); + map = GetMap(sourceGenericArg, targetGenericArg); + + // if there is a constructor for the underlying type, create & invoke the map + if (ctor != null && map != null) + { + // register (for next time) and do it now (for this time) + object NCtor(object s, MapperContext c) => MapEnumerableInternal((IEnumerable) s, targetGenericArg, ctor, map, c); + DefineCtors(sourceType)[targetType] = NCtor; + DefineMaps(sourceType)[targetType] = Identity; + 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}."); + } + + throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}."); + } + + private TTarget MapEnumerableInternal(IEnumerable source, Type targetGenericArg, Func ctor, Action map, MapperContext context) + { + var targetList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericArg)); + + foreach (var sourceItem in source) + { + var targetItem = ctor(sourceItem, context); + map(sourceItem, targetItem, context); + targetList.Add(targetItem); + } + + object target = targetList; + + if (typeof(TTarget).IsArray) + { + var elementType = typeof(TTarget).GetElementType(); + if (elementType == null) throw new Exception("panic"); + var targetArray = Array.CreateInstance(elementType, targetList.Count); + targetList.CopyTo(targetArray, 0); + target = targetArray; + } + + return (TTarget) target; + } + + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// The target object. + public TTarget Map(TSource source, TTarget target) + => Map(source, target, new MapperContext(this)); + + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(TSource source, TTarget target, Action f) + { + var context = new MapperContext(this); + f(context); + return Map(source, target, context); + } + + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context. + /// The target object. + public TTarget Map(TSource source, TTarget target, MapperContext context) + { + var sourceType = typeof(TSource); + var targetType = typeof(TTarget); + + var map = GetMap(sourceType, targetType); + + // if there is a direct map, map + if (map != null) + { + map(source, target, context); + return target; + } + + // we cannot really map to an existing enumerable - give up + + throw new InvalidOperationException($"Don't know how to map {typeof(TSource).FullName} to {typeof(TTarget).FullName}."); + } + + private Func GetCtor(Type sourceType, Type targetType) + { + if (_ctors.TryGetValue(sourceType, out var sourceCtor) && sourceCtor.TryGetValue(targetType, out var ctor)) + return ctor; + + ctor = null; + foreach (var (stype, sctors) in _ctors) + { + if (!stype.IsAssignableFrom(sourceType)) continue; + if (!sctors.TryGetValue(targetType, out ctor)) continue; + + sourceCtor = sctors; + break; + } + + if (ctor == null) return null; + + _ctors[sourceType] = sourceCtor; + return ctor; + } + + private Action GetMap(Type sourceType, Type targetType) + { + if (_maps.TryGetValue(sourceType, out var sourceMap) && sourceMap.TryGetValue(targetType, out var map)) + return map; + + map = null; + foreach (var (stype, smap) in _maps) + { + if (!stype.IsAssignableFrom(sourceType)) continue; + + // TODO: consider looking for assignable types for target too? + if (!smap.TryGetValue(targetType, out map)) continue; + + sourceMap = smap; + break; + } + + if (map == null) return null; + + _maps[sourceType] = sourceMap; + return map; + } + + private static bool IsEnumerableOrArrayOfType(Type type) + { + if (type.IsArray && type.GetArrayRank() == 1) return true; + if (type.IsGenericType && type.GenericTypeArguments.Length == 1) return true; + return false; + } + + private static Type GetEnumerableOrArrayTypeArgument(Type type) + { + if (type.IsArray) return type.GetElementType(); + if (type.IsGenericType) return type.GenericTypeArguments[0]; + throw new Exception("panic"); + } + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source) + { + return source.Select(Map).ToList(); + } + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A mapper context preparation method. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source, Action f) + { + var context = new MapperContext(this); + f(context); + return source.Select(x => Map(x, context)).ToList(); + } + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A mapper context. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source, MapperContext context) + { + return source.Select(x => Map(x, context)).ToList(); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index d8283fd112..eab7afe308 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -47,7 +47,7 @@ namespace Umbraco.Core.Migrations.Install typeof (LogDto), typeof (MacroDto), typeof (MacroPropertyDto), - typeof (MemberTypeDto), + typeof (MemberPropertyTypeDto), typeof (MemberDto), typeof (Member2MemberGroupDto), typeof (PropertyTypeGroupDto), diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index ad307324ea..fa24cba21f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Migrations.Upgrade.V_7_12_0; using Umbraco.Core.Migrations.Upgrade.V_7_14_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; +using Umbraco.Core.Migrations.Upgrade.V_8_0_1; namespace Umbraco.Core.Migrations.Upgrade { @@ -137,6 +138,8 @@ namespace Umbraco.Core.Migrations.Upgrade To("{E0CBE54D-A84F-4A8F-9B13-900945FD7ED9}"); To("{78BAF571-90D0-4D28-8175-EF96316DA789}"); + To("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}"); + //FINAL @@ -167,7 +170,7 @@ namespace Umbraco.Core.Migrations.Upgrade From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0 - + // 7.14.0 has migrations, handle it... // clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then // clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations), diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs index 4e1a7d1470..2b5f481769 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs @@ -13,8 +13,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 { var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberType) && x.ColumnName.InvariantEquals("isSensitive")) == false) - AddColumn("isSensitive"); + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberPropertyType) && x.ColumnName.InvariantEquals("isSensitive")) == false) + AddColumn("isSensitive"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs new file mode 100644 index 0000000000..f6850eb254 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Migrations.PostMigrations; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_1 +{ + public class ChangeNuCacheJsonFormat : MigrationBase + { + public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + // nothing - just adding the post-migration + Context.AddPostMigration(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs new file mode 100644 index 0000000000..0ceb366e1c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Migrations.PostMigrations; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class ChangeNuCacheJsonFormat : MigrationBase + { + public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + // nothing - just adding the post-migration + Context.AddPostMigration(); + } + } +} diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index c0e4cd7032..bf7228ca47 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -1,11 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Collections; -using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; namespace Umbraco.Core.Models { @@ -170,23 +166,23 @@ namespace Umbraco.Core.Models /// Sets the publishing values for names and properties. /// /// - /// + /// /// A value indicating whether it was possible to publish the names and values for the specified /// culture(s). The method may fail if required names are not set, but it does NOT validate property data - public static bool PublishCulture(this IContent content, string culture = "*") + public static bool PublishCulture(this IContent content, CultureImpact impact) { - culture = culture.NullOrWhiteSpaceAsNull(); + if (impact == null) throw new ArgumentNullException(nameof(impact)); // the variation should be supported by the content type properties // if the content type is invariant, only '*' and 'null' is ok // if the content type varies, everything is ok because some properties may be invariant - 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 (!content.ContentType.SupportsPropertyVariation(impact.Culture, "*", true)) + throw new NotSupportedException($"Culture \"{impact.Culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); - var alsoInvariant = false; - if (culture == "*") // all cultures + // set names + if (impact.ImpactsAllCultures) { - foreach (var c in content.AvailableCultures) + foreach (var c in content.AvailableCultures) // does NOT contain the invariant culture { var name = content.GetCultureName(c); if (string.IsNullOrWhiteSpace(name)) @@ -194,26 +190,31 @@ namespace Umbraco.Core.Models content.SetPublishInfo(c, name, DateTime.Now); } } - else if (culture == null) // invariant culture + else if (impact.ImpactsOnlyInvariantCulture) { if (string.IsNullOrWhiteSpace(content.Name)) return false; // PublishName set by repository - nothing to do here } - else // one single culture + else if (impact.ImpactsExplicitCulture) { - var name = content.GetCultureName(culture); + var name = content.GetCultureName(impact.Culture); if (string.IsNullOrWhiteSpace(name)) return false; - content.SetPublishInfo(culture, name, DateTime.Now); - alsoInvariant = true; // we also want to publish invariant values + content.SetPublishInfo(impact.Culture, name, DateTime.Now); } - // property.PublishValues only publishes what is valid, variation-wise + // set values + // property.PublishValues only publishes what is valid, variation-wise, + // but accepts any culture arg: null, all, specific foreach (var property in content.Properties) { - property.PublishValues(culture); - if (alsoInvariant) + // for the specified culture (null or all or specific) + property.PublishValues(impact.Culture); + + // maybe the specified culture did not impact the invariant culture, so PublishValues + // above would skip it, yet it *also* impacts invariant properties + if (impact.ImpactsAlsoInvariantProperties) property.PublishValues(null); } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index ed8a098299..d4e35cccdf 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -210,9 +210,7 @@ namespace Umbraco.Core.Models return Variations.ValidateVariation(culture, segment, false, true, false); } - /// - /// List of PropertyGroups available on this ContentType - /// + /// /// /// A PropertyGroup corresponds to a Tab in the UI /// Marked DoNotClone because we will manually deal with cloning and the event handlers @@ -230,9 +228,7 @@ namespace Umbraco.Core.Models } } - /// - /// Gets all property types, across all property groups. - /// + /// [IgnoreDataMember] [DoNotClone] public IEnumerable PropertyTypes @@ -243,12 +239,7 @@ namespace Umbraco.Core.Models } } - /// - /// Gets or sets the property types that are not in a group. - /// - /// - /// Marked DoNotClone because we will manually deal with cloning and the event handlers - /// + /// [DoNotClone] public IEnumerable NoGroupPropertyTypes { diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 7497c100bc..ff61a15979 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -43,9 +43,7 @@ namespace Umbraco.Core.Models } } - /// - /// Gets the property groups for the entire composition. - /// + /// [IgnoreDataMember] public IEnumerable CompositionPropertyGroups { @@ -76,9 +74,7 @@ namespace Umbraco.Core.Models } } - /// - /// Gets the property types for the entire composition. - /// + /// [IgnoreDataMember] public IEnumerable CompositionPropertyTypes { diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs new file mode 100644 index 0000000000..ca18985941 --- /dev/null +++ b/src/Umbraco.Core/Models/CultureImpact.cs @@ -0,0 +1,257 @@ +using System; +using System.Linq; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the impact of a culture set. + /// + /// + /// A set of cultures can be either all cultures (including the invariant culture), or + /// the invariant culture, or a specific culture. + /// + internal class CultureImpact + { + /// + /// Utility method to return the culture used for invariant property errors based on what cultures are being actively saved, + /// the default culture and the state of the current content item + /// + /// + /// + /// + /// + public static string GetCultureForInvariantErrors(IContent content, string[] savingCultures, string defaultCulture) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + if (savingCultures == null) throw new ArgumentNullException(nameof(savingCultures)); + if (savingCultures.Length == 0) throw new ArgumentException(nameof(savingCultures)); + + var cultureForInvariantErrors = savingCultures.Any(x => x.InvariantEquals(defaultCulture)) + //the default culture is being flagged for saving so use it + ? defaultCulture + //If the content has no published version, we need to affiliate validation with the first variant being saved. + //If the content has a published version we will not affiliate the validation with any culture (null) + : !content.Published ? savingCultures[0] : null; + + return cultureForInvariantErrors; + } + + /// + /// Initializes a new instance of the class. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + private CultureImpact(string culture, bool isDefault = false) + { + if (culture != null && culture.IsNullOrWhiteSpace()) + throw new ArgumentException("Culture \"\" is not valid here."); + + Culture = culture; + + if ((culture == null || culture == "*") && isDefault) + throw new ArgumentException("The invariant or 'all' culture can not be the default culture."); + + ImpactsOnlyDefaultCulture = isDefault; + } + + /// + /// Gets the impact of 'all' cultures (including the invariant culture). + /// + public static CultureImpact All { get; } = new CultureImpact("*"); + + /// + /// Gets the impact of the invariant culture. + /// + public static CultureImpact Invariant { get; } = new CultureImpact(null); + + /// + /// Creates an impact instance representing the impact of a specific culture. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + public static CultureImpact Explicit(string culture, bool isDefault) + { + if (culture == null) + throw new ArgumentException("Culture is not explicit."); + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentException("Culture \"\" is not explicit."); + if (culture == "*") + throw new ArgumentException("Culture \"*\" is not explicit."); + + return new CultureImpact(culture, isDefault); + } + + /// + /// Creates an impact instance representing the impact of a culture set, + /// in the context of a content item variation. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + /// The content item. + /// + /// Validates that the culture is compatible with the variation. + /// + public static CultureImpact Create(string culture, bool isDefault, IContent content) + { + // throws if not successful + TryCreate(culture, isDefault, content.ContentType.Variations, true, out var impact); + return impact; + } + + /// + /// Tries to create an impact instance representing the impact of a culture set, + /// in the context of a content item variation. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + /// A content variation. + /// A value indicating whether to throw if the impact cannot be created. + /// The impact if it could be created, otherwise null. + /// A value indicating whether the impact could be created. + /// + /// Validates that the culture is compatible with the variation. + /// + internal static bool TryCreate(string culture, bool isDefault, ContentVariation variation, bool throwOnFail, out CultureImpact impact) + { + impact = null; + + // if culture is invariant... + if (culture == null) + { + // ... then variation must not vary by culture ... + if (variation.VariesByCulture()) + { + if (throwOnFail) + throw new InvalidOperationException("The invariant culture is not compatible with a varying variation."); + return false; + } + + // ... and it cannot be default + if (isDefault) + { + if (throwOnFail) + throw new InvalidOperationException("The invariant culture can not be the default culture."); + return false; + } + + impact = Invariant; + return true; + } + + // if culture is 'all'... + if (culture == "*") + { + // ... it cannot be default + if (isDefault) + { + if (throwOnFail) + throw new InvalidOperationException("The 'all' culture can not be the default culture."); + return false; + } + + // if variation does not vary by culture, then impact is invariant + impact = variation.VariesByCulture() ? All : Invariant; + return true; + } + + // neither null nor "*" - cannot be the empty string + if (culture.IsNullOrWhiteSpace()) + { + if (throwOnFail) + throw new ArgumentException("Cannot be the empty string.", nameof(culture)); + return false; + } + + // if culture is specific, then variation must vary + if (!variation.VariesByCulture()) + { + if (throwOnFail) + throw new InvalidOperationException($"The variant culture {culture} is not compatible with an invariant variation."); + return false; + } + + // return specific impact + impact = new CultureImpact(culture, isDefault); + return true; + } + + /// + /// Gets the culture code. + /// + /// + /// Can be null (invariant) or * (all cultures) or a specific culture code. + /// + public string Culture { get; } + + /// + /// Gets a value indicating whether this impact impacts all cultures, including, + /// indirectly, the invariant culture. + /// + public bool ImpactsAllCultures => Culture == "*"; + + /// + /// Gets a value indicating whether this impact impacts only the invariant culture, + /// directly, not because all cultures are impacted. + /// + public bool ImpactsOnlyInvariantCulture => Culture == null; + + /// + /// Gets a value indicating whether this impact impacts an implicit culture. + /// + /// And then it does not impact the invariant culture. The impacted + /// explicit culture could be the default culture. + public bool ImpactsExplicitCulture => Culture != null && Culture != "*"; + + /// + /// Gets a value indicating whether this impact impacts the default culture, directly, + /// not because all cultures are impacted. + /// + public bool ImpactsOnlyDefaultCulture {get; } + + /// + /// Gets a value indicating whether this impact impacts the invariant properties, either + /// directly, or because all cultures are impacted, or because the default culture is impacted. + /// + public bool ImpactsInvariantProperties => Culture == null || Culture == "*" || ImpactsOnlyDefaultCulture; + + /// + /// Gets a value indicating whether this also impact impacts the invariant properties, + /// even though it does not impact the invariant culture, neither directly (ImpactsInvariantCulture) + /// nor indirectly (ImpactsAllCultures). + /// + public bool ImpactsAlsoInvariantProperties => !ImpactsOnlyInvariantCulture && + !ImpactsAllCultures && + ImpactsOnlyDefaultCulture; + + public Behavior CultureBehavior + { + get + { + //null can only be invariant + if (Culture == null) return Behavior.InvariantCulture | Behavior.InvariantProperties; + + // * is All which means its also invariant properties since this will include the default language + if (Culture == "*") return (Behavior.AllCultures | Behavior.InvariantProperties); + + //else it's explicit + var result = Behavior.ExplicitCulture; + + //if the explicit culture is the default, then the behavior is also InvariantProperties + if (ImpactsOnlyDefaultCulture) + result |= Behavior.InvariantProperties; + + return result; + } + } + + + [Flags] + public enum Behavior : byte + { + AllCultures = 1, + InvariantCulture = 2, + ExplicitCulture = 4, + InvariantProperties = 8 + } + } +} diff --git a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs index a827f7af14..5cce9777cb 100644 --- a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs +++ b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Models /// * when the setter performs additional required logic other than just setting the underlying field /// /// - internal class DoNotCloneAttribute : Attribute + public class DoNotCloneAttribute : Attribute { } diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 215c8532c1..5f1fe6ed49 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -96,17 +96,17 @@ namespace Umbraco.Core.Models IEnumerable AllowedContentTypes { get; set; } /// - /// Gets or Sets a collection of Property Groups + /// Gets or sets the local property groups. /// PropertyGroupCollection PropertyGroups { get; set; } /// - /// Gets all property types, across all property groups. + /// Gets all local property types belonging to a group, across all local property groups. /// IEnumerable PropertyTypes { get; } /// - /// Gets or sets the property types that are not in a group. + /// Gets or sets the local property types that do not belong to a group. /// IEnumerable NoGroupPropertyTypes { get; set; } diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 0eb53428d1..8dc056d555 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Models.Identity if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - var user = new BackOfficeIdentityUser(); + var user = new BackOfficeIdentityUser(Array.Empty()); user.DisableChangeTracking(); user._userName = username; user._email = email; @@ -54,16 +54,19 @@ namespace Umbraco.Core.Models.Identity return user; } - private BackOfficeIdentityUser() + private BackOfficeIdentityUser(IReadOnlyUserGroup[] groups) { - _startMediaIds = new int[] { }; - _startContentIds = new int[] { }; - _groups = new IReadOnlyUserGroup[] { }; - _allowedSections = new string[] { }; + _startMediaIds = Array.Empty(); + _startContentIds = Array.Empty(); + _allowedSections = Array.Empty(); _culture = Current.Configs.Global().DefaultUILanguage; // TODO: inject - _groups = new IReadOnlyUserGroup[0]; + + // must initialize before setting groups _roles = new ObservableCollection>(); _roles.CollectionChanged += _roles_CollectionChanged; + + // use the property setters - they do more than just setting a field + Groups = groups; } /// @@ -72,19 +75,10 @@ namespace Umbraco.Core.Models.Identity /// /// public BackOfficeIdentityUser(int userId, IEnumerable groups) + : this(groups.ToArray()) { - _startMediaIds = new int[] { }; - _startContentIds = new int[] { }; - _groups = new IReadOnlyUserGroup[] { }; - _allowedSections = new string[] { }; - _culture = Current.Configs.Global().DefaultUILanguage; // TODO: inject - _groups = groups.ToArray(); - _roles = new ObservableCollection>(_groups.Select(x => new IdentityUserRole - { - RoleId = x.Alias, - UserId = userId.ToString() - })); - _roles.CollectionChanged += _roles_CollectionChanged; + // use the property setters - they do more than just setting a field + Id = userId; } /// @@ -226,6 +220,8 @@ namespace Umbraco.Core.Models.Identity //so they recalculate _allowedSections = null; + _groups = value; + //now clear all roles and re-add them _roles.CollectionChanged -= _roles_CollectionChanged; _roles.Clear(); diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs new file mode 100644 index 0000000000..57e1c9ee5c --- /dev/null +++ b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs @@ -0,0 +1,80 @@ +using System; +using Umbraco.Core.Configuration; +using Umbraco.Core.Mapping; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models.Identity +{ + public class IdentityMapDefinition : IMapDefinition + { + private readonly ILocalizedTextService _textService; + private readonly IEntityService _entityService; + private readonly IGlobalSettings _globalSettings; + + public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings) + { + _textService = textService; + _entityService = entityService; + _globalSettings = globalSettings; + } + + public void DefineMaps(UmbracoMapper mapper) + { + mapper.Define( + (source, context) => + { + var target = new BackOfficeIdentityUser(source.Id, source.Groups); + target.DisableChangeTracking(); + return target; + }, + (source, target, context) => + { + Map(source, target); + target.ResetDirtyProperties(true); + target.EnableChangeTracking(); + }); + } + + // Umbraco.Code.MapAll -Id -Groups -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled + private void Map(IUser source, BackOfficeIdentityUser target) + { + // well, the ctor has been fixed + /* + // these two are already set in ctor but BackOfficeIdentityUser ctor is CompletelyBroken + target.Id = source.Id; + target.Groups = source.Groups.ToArray(); + */ + + target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService); + target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService); + target.Email = source.Email; + target.UserName = source.Username; + target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime(); + target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime(); + target.EmailConfirmed = source.EmailConfirmedDate.HasValue; + target.Name = source.Name; + target.AccessFailedCount = source.FailedPasswordAttempts; + target.PasswordHash = GetPasswordHash(source.RawPasswordValue); + target.StartContentIds = source.StartContentIds; + target.StartMediaIds = source.StartMediaIds; + target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); // project CultureInfo to string + target.IsApproved = source.IsApproved; + target.SecurityStamp = source.SecurityStamp; + target.LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null; + + // this was in AutoMapper but does not have a setter anyways + //target.AllowedSections = source.AllowedSections.ToArray(), + + // these were marked as ignored for AutoMapper but don't have a setter anyways + //target.Logins =; + //target.Claims =; + //target.Roles =; + } + + private static string GetPasswordHash(string storedPass) + { + return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass; + } + } +} diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs deleted file mode 100644 index 81069bd74c..0000000000 --- a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Linq; -using AutoMapper; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models.Identity -{ - public class IdentityMapperProfile : Profile - { - public IdentityMapperProfile(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings) - { - CreateMap() - .BeforeMap((src, dest) => - { - dest.DisableChangeTracking(); - }) - .ConstructUsing(src => new BackOfficeIdentityUser(src.Id, src.Groups)) - .ForMember(dest => dest.LastLoginDateUtc, opt => opt.MapFrom(src => src.LastLoginDate.ToUniversalTime())) - .ForMember(user => user.LastPasswordChangeDateUtc, expression => expression.MapFrom(user => user.LastPasswordChangeDate.ToUniversalTime())) - .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) - .ForMember(dest => dest.EmailConfirmed, opt => opt.MapFrom(src => src.EmailConfirmedDate.HasValue)) - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) - .ForMember(dest => dest.LockoutEndDateUtc, opt => opt.MapFrom(src => src.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null)) - .ForMember(dest => dest.IsApproved, opt => opt.MapFrom(src => src.IsApproved)) - .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.Username)) - .ForMember(dest => dest.PasswordHash, opt => opt.MapFrom(user => GetPasswordHash(user.RawPasswordValue))) - .ForMember(dest => dest.Culture, opt => opt.MapFrom(src => src.GetUserCulture(textService, globalSettings))) - .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) - .ForMember(dest => dest.StartMediaIds, opt => opt.MapFrom(src => src.StartMediaIds)) - .ForMember(dest => dest.StartContentIds, opt => opt.MapFrom(src => src.StartContentIds)) - .ForMember(dest => dest.AccessFailedCount, opt => opt.MapFrom(src => src.FailedPasswordAttempts)) - .ForMember(dest => dest.CalculatedContentStartNodeIds, opt => opt.MapFrom(src => src.CalculateContentStartNodeIds(entityService))) - .ForMember(dest => dest.CalculatedMediaStartNodeIds, opt => opt.MapFrom(src => src.CalculateMediaStartNodeIds(entityService))) - .ForMember(dest => dest.AllowedSections, opt => opt.MapFrom(src => src.AllowedSections.ToArray())) - .ForMember(dest => dest.LockoutEnabled, opt => opt.Ignore()) - .ForMember(dest => dest.Logins, opt => opt.Ignore()) - .ForMember(dest => dest.EmailConfirmed, opt => opt.Ignore()) - .ForMember(dest => dest.PhoneNumber, opt => opt.Ignore()) - .ForMember(dest => dest.PhoneNumberConfirmed, opt => opt.Ignore()) - .ForMember(dest => dest.TwoFactorEnabled, opt => opt.Ignore()) - .ForMember(dest => dest.Roles, opt => opt.Ignore()) - .ForMember(dest => dest.Claims, opt => opt.Ignore()) - .AfterMap((src, dest) => - { - dest.ResetDirtyProperties(true); - dest.EnableChangeTracking(); - }); - } - - private static string GetPasswordHash(string storedPass) - { - return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass; - } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index a5669d1a9e..749b37a41a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -11,14 +11,14 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Initializes a new instance of the class. /// - public PublishedCultureInfo(string culture, string name, DateTime date) + public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date) { if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); Culture = culture; Name = name; - UrlSegment = name.ToUrlSegment(culture); + UrlSegment = urlSegment; Date = date; } diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index f9aa1113b3..0f83cf78a4 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -151,22 +151,22 @@ namespace Umbraco.Core.Models internal static bool HasContentRootAccess(this IUser user, IEntityService entityService) { - return ContentPermissionsHelper.HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); } internal static bool HasContentBinAccess(this IUser user, IEntityService entityService) { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContent.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); } internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService) { - return ContentPermissionsHelper.HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); } internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService) { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMedia.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); } internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService) @@ -327,7 +327,7 @@ namespace Umbraco.Core.Models ? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path) : new Dictionary(); - paths[Constants.System.Root] = Constants.System.Root.ToString(); // entityService does not get that one + paths[Constants.System.Root] = Constants.System.RootString; // entityService does not get that one var binPath = GetBinPath(objectType); diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index b62a99ce83..f430a8894c 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -57,7 +57,7 @@ namespace Umbraco.Core public const string MacroProperty = /*TableNamePrefix*/ "cms" + "MacroProperty"; public const string Member = /*TableNamePrefix*/ "cms" + "Member"; - public const string MemberType = /*TableNamePrefix*/ "cms" + "MemberType"; + public const string MemberPropertyType = /*TableNamePrefix*/ "cms" + "MemberType"; public const string Member2MemberGroup = /*TableNamePrefix*/ "cms" + "Member2MemberGroup"; public const string Access = TableNamePrefix + "Access"; @@ -69,7 +69,7 @@ namespace Umbraco.Core public const string Tag = /*TableNamePrefix*/ "cms" + "Tags"; public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship"; - + public const string KeyValue = TableNamePrefix + "KeyValue"; public const string AuditEntry = TableNamePrefix + "Audit"; diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs index 4f3a67aa91..e7a14a26e2 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs @@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Dtos public byte Variations { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")] public NodeDto NodeDto { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs index d270c7b732..24f0e07295 100644 --- a/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Dtos public string Configuration { get; set; } [ResultColumn] - [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")] public NodeDto NodeDto { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/MemberPropertyTypeDto.cs similarity index 88% rename from src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/MemberPropertyTypeDto.cs index 545f92bb82..7186455489 100644 --- a/src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/MemberPropertyTypeDto.cs @@ -3,10 +3,10 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos { - [TableName(Constants.DatabaseSchema.Tables.MemberType)] + [TableName(Constants.DatabaseSchema.Tables.MemberPropertyType)] [PrimaryKey("pk")] [ExplicitColumns] - internal class MemberTypeDto + internal class MemberPropertyTypeDto { [Column("pk")] [PrimaryKeyColumn] diff --git a/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs deleted file mode 100644 index c4ea6a10fd..0000000000 --- a/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using NPoco; - -namespace Umbraco.Core.Persistence.Dtos -{ - [TableName(Constants.DatabaseSchema.Tables.Node)] - [PrimaryKey("id")] - [ExplicitColumns] - internal class MemberTypeReadOnlyDto - { - /* from umbracoNode */ - [Column("id")] - public int NodeId { get; set; } - - [Column("trashed")] - public bool Trashed { get; set; } - - [Column("parentID")] - public int ParentId { get; set; } - - [Column("nodeUser")] - public int? UserId { get; set; } - - [Column("level")] - public short Level { get; set; } - - [Column("path")] - public string Path { get; set; } - - [Column("sortOrder")] - public int SortOrder { get; set; } - - [Column("uniqueID")] - public Guid? UniqueId { get; set; } - - [Column("text")] - public string Text { get; set; } - - [Column("nodeObjectType")] - public Guid? NodeObjectType { get; set; } - - [Column("createDate")] - public DateTime CreateDate { get; set; } - - /* cmsContentType */ - [Column("pk")] - public int PrimaryKey { get; set; } - - [Column("alias")] - public string Alias { get; set; } - - [Column("icon")] - public string Icon { get; set; } - - [Column("thumbnail")] - public string Thumbnail { get; set; } - - [Column("description")] - public string Description { get; set; } - - [Column("isContainer")] - public bool IsContainer { get; set; } - - [Column("allowAtRoot")] - public bool AllowAtRoot { get; set; } - - /* PropertyTypes */ - // TODO: Add PropertyTypeDto (+MemberTypeDto and DataTypeDto as one) ReadOnly list - [ResultColumn] - [Reference(ReferenceType.Many, ReferenceMemberName = "ContentTypeId")] - public List PropertyTypes { get; set; } - - /* PropertyTypeGroups */ - // TODO: Add PropertyTypeGroupDto ReadOnly list - [ResultColumn] - [Reference(ReferenceType.Many, ReferenceMemberName = "ContentTypeNodeId")] - public List PropertyTypeGroups { get; set; } - } -} diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeCommonDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeCommonDto.cs new file mode 100644 index 0000000000..040123353e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeCommonDto.cs @@ -0,0 +1,18 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + // this is PropertyTypeDto + the special property type fields for members + // it is used for querying everything needed for a property type, at once + internal class PropertyTypeCommonDto : PropertyTypeDto + { + [Column("memberCanEdit")] + public bool CanEdit { get; set; } + + [Column("viewOnProfile")] + public bool ViewOnProfile { get; set; } + + [Column("isSensitive")] + public bool IsSensitive { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 7a04a6d0d9..54cfee0ffa 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -67,16 +67,28 @@ namespace Umbraco.Core.Persistence.Factories public static IMemberType BuildMemberTypeEntity(ContentTypeDto dto) { - throw new NotImplementedException(); + var contentType = new MemberType(dto.NodeDto.ParentId); + try + { + contentType.DisableChangeTracking(); + BuildCommonEntity(contentType, dto, false); + contentType.ResetDirtyProperties(false); + } + finally + { + contentType.EnableChangeTracking(); + } + + return contentType; } - public static IEnumerable BuildMemberTypeDtos(IMemberType entity) + public static IEnumerable BuildMemberPropertyTypeDtos(IMemberType entity) { var memberType = entity as MemberType; if (memberType == null || memberType.PropertyTypes.Any() == false) - return Enumerable.Empty(); + return Enumerable.Empty(); - var dtos = memberType.PropertyTypes.Select(x => new MemberTypeDto + var dtos = memberType.PropertyTypes.Select(x => new MemberPropertyTypeDto { NodeId = entity.Id, PropertyTypeId = x.Id, @@ -91,7 +103,7 @@ namespace Umbraco.Core.Persistence.Factories #region Common - private static void BuildCommonEntity(ContentTypeBase entity, ContentTypeDto dto) + private static void BuildCommonEntity(ContentTypeBase entity, ContentTypeDto dto, bool setVariations = true) { entity.Id = dto.NodeDto.NodeId; entity.Key = dto.NodeDto.UniqueId; @@ -102,6 +114,7 @@ namespace Umbraco.Core.Persistence.Factories entity.SortOrder = dto.NodeDto.SortOrder; entity.Description = dto.Description; entity.CreateDate = dto.NodeDto.CreateDate; + entity.UpdateDate = dto.NodeDto.CreateDate; entity.Path = dto.NodeDto.Path; entity.Level = dto.NodeDto.Level; entity.CreatorId = dto.NodeDto.UserId ?? Constants.Security.UnknownUserId; @@ -109,7 +122,9 @@ namespace Umbraco.Core.Persistence.Factories entity.IsContainer = dto.IsContainer; entity.IsElement = dto.IsElement; entity.Trashed = dto.NodeDto.Trashed; - entity.Variations = (ContentVariation) dto.Variations; + + if (setVariations) + entity.Variations = (ContentVariation) dto.Variations; } public static ContentTypeDto BuildContentTypeDto(IContentTypeBase entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs deleted file mode 100644 index c7ce98a89c..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Repositories.Implement; - -namespace Umbraco.Core.Persistence.Factories -{ - internal static class MemberTypeReadOnlyFactory - { - public static IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving) - { - var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); - needsSaving = false; - - var memberType = new MemberType(dto.ParentId); - - try - { - memberType.DisableChangeTracking(); - - memberType.Alias = dto.Alias; - memberType.AllowedAsRoot = dto.AllowAtRoot; - memberType.CreateDate = dto.CreateDate; - memberType.CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0; - memberType.Description = dto.Description; - memberType.Icon = dto.Icon; - memberType.Id = dto.NodeId; - memberType.IsContainer = dto.IsContainer; - memberType.Key = dto.UniqueId.Value; - memberType.Level = dto.Level; - memberType.Name = dto.Text; - memberType.Path = dto.Path; - memberType.SortOrder = dto.SortOrder; - memberType.Thumbnail = dto.Thumbnail; - memberType.Trashed = dto.Trashed; - memberType.UpdateDate = dto.CreateDate; - memberType.AllowedContentTypes = Enumerable.Empty(); - - var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); - memberType.PropertyGroups = propertyTypeGroupCollection; - - var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); - - //By Convention we add 9 standard PropertyTypes - This is only here to support loading of types that didn't have these conventions before. - foreach (var standardPropertyType in standardPropertyTypes) - { - if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; - - // beware! - // means that we can return a memberType "from database" that has some property types - // that do *not* come from the database and therefore are incomplete eg have no key, - // no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation - needsSaving = true; - - //Add the standard PropertyType to the current list - propertyTypes.Add(standardPropertyType.Value); - - //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, - new MemberTypePropertyProfileAccess(false, false, false)); - } - memberType.NoGroupPropertyTypes = propertyTypes; - - return memberType; - } - finally - { - memberType.EnableChangeTracking(); - } - } - - private static PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) - { - // see PropertyGroupFactory, repeating code here... - - var propertyGroups = new PropertyGroupCollection(); - foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) - { - var group = new PropertyGroup(MemberType.SupportsPublishingConst); - - // if the group is defined on the current member type, - // assign its identifier, else it will be zero - if (groupDto.ContentTypeNodeId == memberType.Id) - { - // note: no idea why Id is nullable here, but better check - if (groupDto.Id.HasValue == false) - throw new Exception("GroupDto.Id has no value."); - group.Id = groupDto.Id.Value; - } - - group.Key = groupDto.UniqueId; - group.Name = groupDto.Text; - group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(MemberType.SupportsPublishingConst); - - //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded - var localGroupDto = groupDto; - var typeDtos = dto.PropertyTypes.Where(x => x.Id.HasValue && x.Id > 0 && x.PropertyTypeGroupId.HasValue && x.PropertyTypeGroupId.Value == localGroupDto.Id.Value); - foreach (var typeDto in typeDtos) - { - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); - - var tempGroupDto = groupDto; - - //ensures that any built-in membership properties have their correct dbtype assigned no matter - //what the underlying data type is - var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( - typeDto.Alias, - typeDto.DbType.EnumParse(true), - standardProps); - - var propertyType = new PropertyType( - typeDto.PropertyEditorAlias, - propDbType.Result, - //This flag tells the property type that it has an explicit dbtype and that it cannot be changed - // which is what we want for the built-in properties. - propDbType.Success, - typeDto.Alias) - { - DataTypeId = typeDto.DataTypeId, - Description = typeDto.Description, - Id = typeDto.Id.Value, - Name = typeDto.Name, - Mandatory = typeDto.Mandatory, - SortOrder = typeDto.SortOrder, - ValidationRegExp = typeDto.ValidationRegExp, - PropertyGroupId = new Lazy(() => tempGroupDto.Id.Value), - CreateDate = memberType.CreateDate, - UpdateDate = memberType.UpdateDate, - Key = typeDto.UniqueId - }; - - // reset dirty initial properties (U4-1946) - propertyType.ResetDirtyProperties(false); - group.PropertyTypes.Add(propertyType); - } - - // reset dirty initial properties (U4-1946) - group.ResetDirtyProperties(false); - propertyGroups.Add(group); - } - - return propertyGroups; - } - - private static List GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) - { - //Find PropertyTypes that does not belong to a PropertyTypeGroup - var propertyTypes = new List(); - foreach (var typeDto in dto.PropertyTypes.Where(x => (x.PropertyTypeGroupId.HasValue == false || x.PropertyTypeGroupId.Value == 0) && x.Id.HasValue)) - { - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); - - //ensures that any built-in membership properties have their correct dbtype assigned no matter - //what the underlying data type is - var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( - typeDto.Alias, - typeDto.DbType.EnumParse(true), - standardProps); - - var propertyType = new PropertyType( - typeDto.PropertyEditorAlias, - propDbType.Result, - //This flag tells the property type that it has an explicit dbtype and that it cannot be changed - // which is what we want for the built-in properties. - propDbType.Success, - typeDto.Alias) - { - DataTypeId = typeDto.DataTypeId, - Description = typeDto.Description, - Id = typeDto.Id.Value, - Mandatory = typeDto.Mandatory, - Name = typeDto.Name, - SortOrder = typeDto.SortOrder, - ValidationRegExp = typeDto.ValidationRegExp, - PropertyGroupId = null, - CreateDate = dto.CreateDate, - UpdateDate = dto.CreateDate, - Key = typeDto.UniqueId - }; - - propertyTypes.Add(propertyType); - } - return propertyTypes; - } - - } -} diff --git a/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs index 0465086810..33bcdad975 100644 --- a/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -7,18 +8,18 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(PublicAccessEntry))] public sealed class AccessMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public AccessMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Key, dto => dto.Id); - CacheMap(src => src.LoginNodeId, dto => dto.LoginNodeId); - CacheMap(src => src.NoAccessNodeId, dto => dto.NoAccessNodeId); - CacheMap(src => src.ProtectedNodeId, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); + DefineMap(nameof(PublicAccessEntry.Key), nameof(AccessDto.Id)); + DefineMap(nameof(PublicAccessEntry.LoginNodeId), nameof(AccessDto.LoginNodeId)); + DefineMap(nameof(PublicAccessEntry.NoAccessNodeId), nameof(AccessDto.NoAccessNodeId)); + DefineMap(nameof(PublicAccessEntry.ProtectedNodeId), nameof(AccessDto.NodeId)); + DefineMap(nameof(PublicAccessEntry.CreateDate), nameof(AccessDto.CreateDate)); + DefineMap(nameof(PublicAccessEntry.UpdateDate), nameof(AccessDto.UpdateDate)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs index 28c7c1eeec..0592a559bb 100644 --- a/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -11,21 +12,21 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(AuditEntry))] public sealed class AuditEntryMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public AuditEntryMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(entity => entity.Id, dto => dto.Id); - CacheMap(entity => entity.PerformingUserId, dto => dto.PerformingUserId); - CacheMap(entity => entity.PerformingDetails, dto => dto.PerformingDetails); - CacheMap(entity => entity.PerformingIp, dto => dto.PerformingIp); - CacheMap(entity => entity.EventDateUtc, dto => dto.EventDateUtc); - CacheMap(entity => entity.AffectedUserId, dto => dto.AffectedUserId); - CacheMap(entity => entity.AffectedDetails, dto => dto.AffectedDetails); - CacheMap(entity => entity.EventType, dto => dto.EventType); - CacheMap(entity => entity.EventDetails, dto => dto.EventDetails); + DefineMap(nameof(AuditEntry.Id), nameof(AuditEntryDto.Id)); + DefineMap(nameof(AuditEntry.PerformingUserId), nameof(AuditEntryDto.PerformingUserId)); + DefineMap(nameof(AuditEntry.PerformingDetails), nameof(AuditEntryDto.PerformingDetails)); + DefineMap(nameof(AuditEntry.PerformingIp), nameof(AuditEntryDto.PerformingIp)); + DefineMap(nameof(AuditEntry.EventDateUtc), nameof(AuditEntryDto.EventDateUtc)); + DefineMap(nameof(AuditEntry.AffectedUserId), nameof(AuditEntryDto.AffectedUserId)); + DefineMap(nameof(AuditEntry.AffectedDetails), nameof(AuditEntryDto.AffectedDetails)); + DefineMap(nameof(AuditEntry.EventType), nameof(AuditEntryDto.EventType)); + DefineMap(nameof(AuditEntry.EventDetails), nameof(AuditEntryDto.EventDetails)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs index ad1964ee07..853cd9f99e 100644 --- a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -8,17 +9,17 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IAuditItem))] public sealed class AuditItemMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public AuditItemMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.Datestamp); - CacheMap(src => src.UserId, dto => dto.UserId); - CacheMap(src => src.AuditType, dto => dto.Header); - CacheMap(src => src.Comment, dto => dto.Comment); + DefineMap(nameof(AuditItem.Id), nameof(LogDto.NodeId)); + DefineMap(nameof(AuditItem.CreateDate), nameof(LogDto.Datestamp)); + DefineMap(nameof(AuditItem.UserId), nameof(LogDto.UserId)); + DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header)); + DefineMap(nameof(AuditItem.Comment), nameof(LogDto.Comment)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index 22421eca53..bfdd60e897 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -1,69 +1,81 @@ using System; using System.Collections.Concurrent; -using System.Linq.Expressions; -using System.Reflection; using NPoco; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Composing; namespace Umbraco.Core.Persistence.Mappers { - public abstract class BaseMapper : IDiscoverable + public abstract class BaseMapper { - protected BaseMapper() + // note: using a Lazy here because during installs, we are resolving the + // mappers way before we have a configured IUmbracoDatabaseFactory, ie way before we + // have an ISqlContext - this is some nasty temporal coupling which we might want to + // cleanup eventually. + + private readonly Lazy _sqlContext; + private readonly object _definedLock = new object(); + private readonly ConcurrentDictionary> _maps; + + private ISqlSyntaxProvider _sqlSyntax; + private bool _defined; + + protected BaseMapper(Lazy sqlContext, ConcurrentDictionary> maps) { - Build(); + _sqlContext = sqlContext; + _maps = maps; } - internal abstract ConcurrentDictionary PropertyInfoCache { get; } + protected abstract void DefineMaps(); - private void Build() + internal string Map(string propertyName) { - BuildMap(); - } - - protected abstract void BuildMap(); - - internal string Map(ISqlSyntaxProvider sqlSyntax, string propertyName, bool throws = false) - { - if (PropertyInfoCache.TryGetValue(propertyName, out var dtoTypeProperty)) - return GetColumnName(sqlSyntax, dtoTypeProperty.Type, dtoTypeProperty.PropertyInfo); - - if (throws) - throw new InvalidOperationException("Could not get the value with the key " + propertyName + " from the property info cache, keys available: " + string.Join(", ", PropertyInfoCache.Keys)); - - return string.Empty; - } - - internal void CacheMap(Expression> sourceMember, Expression> destinationMember) - { - var property = ResolveMapping(sourceMember, destinationMember); - PropertyInfoCache.AddOrUpdate(property.SourcePropertyName, property, (x, y) => property); - } - - internal DtoMapModel ResolveMapping(Expression> sourceMember, Expression> destinationMember) - { - var source = ExpressionHelper.FindProperty(sourceMember); - var destination = (PropertyInfo) ExpressionHelper.FindProperty(destinationMember).Item1; - - if (destination == null) + lock (_definedLock) { - throw new InvalidOperationException("The 'destination' returned was null, cannot resolve the mapping"); + if (!_defined) + { + var sqlContext = _sqlContext.Value; + if (sqlContext == null) + throw new InvalidOperationException("Could not get an ISqlContext."); + _sqlSyntax = sqlContext.SqlSyntax; + + DefineMaps(); + + _defined = true; + } } - return new DtoMapModel(typeof(TDestination), destination, source.Item1.Name); + if (!_maps.TryGetValue(GetType(), out var mapperMaps)) + throw new InvalidOperationException($"No maps defined for mapper {GetType().FullName}."); + if (!mapperMaps.TryGetValue(propertyName, out var mappedName)) + throw new InvalidOperationException($"No map defined by mapper {GetType().FullName} for property {propertyName}."); + return mappedName; } - internal virtual string GetColumnName(ISqlSyntaxProvider sqlSyntax, Type dtoType, PropertyInfo dtoProperty) + protected void DefineMap(string sourceName, string targetName) { - var tableNameAttribute = dtoType.FirstAttribute(); + if (_sqlSyntax == null) + throw new InvalidOperationException("Do not define maps outside of DefineMaps."); + + var targetType = typeof(TTarget); + + // TODO ensure that sourceName is a valid sourceType property (but, slow?) + + var tableNameAttribute = targetType.FirstAttribute(); + if (tableNameAttribute == null) throw new InvalidOperationException($"Type {targetType.FullName} is not marked with a TableName attribute."); var tableName = tableNameAttribute.Value; - var columnAttribute = dtoProperty.FirstAttribute(); - var columnName = columnAttribute.Name; + // TODO maybe get all properties once and then index them + var targetProperty = targetType.GetProperty(targetName); + if (targetProperty == null) throw new InvalidOperationException($"Type {targetType.FullName} does not have a property named {targetName}."); + var columnAttribute = targetProperty.FirstAttribute(); + if (columnAttribute == null) throw new InvalidOperationException($"Property {targetType.FullName}.{targetName} is not marked with a Column attribute."); - var columnMap = sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(columnName); - return columnMap; + var columnName = columnAttribute.Name; + var columnMap = _sqlSyntax.GetQuotedTableName(tableName) + "." + _sqlSyntax.GetQuotedColumnName(columnName); + + var mapperMaps = _maps.GetOrAdd(GetType(), type => new ConcurrentDictionary()); + mapperMaps[sourceName] = columnMap; } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs index 063197d1a2..c7eca9909c 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -11,20 +12,20 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(Consent))] public sealed class ConsentMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public ConsentMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(entity => entity.Id, dto => dto.Id); - CacheMap(entity => entity.Current, dto => dto.Current); - CacheMap(entity => entity.CreateDate, dto => dto.CreateDate); - CacheMap(entity => entity.Source, dto => dto.Source); - CacheMap(entity => entity.Context, dto => dto.Context); - CacheMap(entity => entity.Action, dto => dto.Action); - CacheMap(entity => entity.State, dto => dto.State); - CacheMap(entity => entity.Comment, dto => dto.Comment); + DefineMap(nameof(Consent.Id), nameof(ConsentDto.Id)); + DefineMap(nameof(Consent.Current), nameof(ConsentDto.Current)); + DefineMap(nameof(Consent.CreateDate), nameof(ConsentDto.CreateDate)); + DefineMap(nameof(Consent.Source), nameof(ConsentDto.Source)); + DefineMap(nameof(Consent.Context), nameof(ConsentDto.Context)); + DefineMap(nameof(Consent.Action), nameof(ConsentDto.Action)); + DefineMap(nameof(Consent.State), nameof(ConsentDto.State)); + DefineMap(nameof(Consent.Comment), nameof(ConsentDto.Comment)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs index 2cc3a5b140..f707f27e17 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,36 +13,34 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IContent))] public sealed class ContentMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public ContentMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; + DefineMap(nameof(Content.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(Content.Key), nameof(NodeDto.UniqueId)); - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.Key, dto => dto.UniqueId); + DefineMap(nameof(Content.VersionId), nameof(ContentVersionDto.Id)); + DefineMap(nameof(Content.Name), nameof(ContentVersionDto.Text)); - CacheMap(src => src.VersionId, dto => dto.Id); - CacheMap(src => src.Name, dto => dto.Text); + DefineMap(nameof(Content.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(Content.Level), nameof(NodeDto.Level)); + DefineMap(nameof(Content.Path), nameof(NodeDto.Path)); + DefineMap(nameof(Content.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(Content.Trashed), nameof(NodeDto.Trashed)); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Trashed, dto => dto.Trashed); + DefineMap(nameof(Content.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(Content.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(Content.ContentTypeId), nameof(ContentDto.ContentTypeId)); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); + DefineMap(nameof(Content.UpdateDate), nameof(ContentVersionDto.VersionDate)); + DefineMap(nameof(Content.Published), nameof(DocumentDto.Published)); - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); - CacheMap(src => src.Published, dto => dto.Published); - - //CacheMap(src => src.Name, dto => dto.Alias); + //DefineMap(nameof(Content.Name), nameof(DocumentDto.Alias)); //CacheMap(src => src, dto => dto.Newest); - //CacheMap(src => src.Template, dto => dto.TemplateId); + //DefineMap(nameof(Content.Template), nameof(DocumentDto.TemplateId)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs index a24963bace..b14fc546c8 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,31 +13,29 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IContentType))] public sealed class ContentTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public ContentTypeMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.IsContainer, dto => dto.IsContainer); - CacheMap(src => src.IsElement, dto => dto.IsElement); - CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + DefineMap(nameof(ContentType.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(ContentType.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(ContentType.Level), nameof(NodeDto.Level)); + DefineMap(nameof(ContentType.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(ContentType.Path), nameof(NodeDto.Path)); + DefineMap(nameof(ContentType.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(ContentType.Name), nameof(NodeDto.Text)); + DefineMap(nameof(ContentType.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(ContentType.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(ContentType.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(ContentType.Alias), nameof(ContentTypeDto.Alias)); + DefineMap(nameof(ContentType.AllowedAsRoot), nameof(ContentTypeDto.AllowAtRoot)); + DefineMap(nameof(ContentType.Description), nameof(ContentTypeDto.Description)); + DefineMap(nameof(ContentType.Icon), nameof(ContentTypeDto.Icon)); + DefineMap(nameof(ContentType.IsContainer), nameof(ContentTypeDto.IsContainer)); + DefineMap(nameof(ContentType.IsElement), nameof(ContentTypeDto.IsElement)); + DefineMap(nameof(ContentType.Thumbnail), nameof(ContentTypeDto.Thumbnail)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/DataTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DataTypeMapper.cs index a67f9301b2..5954cb5729 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DataTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DataTypeMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,25 +13,24 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IDataType))] public sealed class DataTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public DataTypeMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.EditorAlias, dto => dto.EditorAlias); - CacheMap(src => src.DatabaseType, dto => dto.DbType); - + DefineMap(nameof(DataType.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(DataType.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(DataType.Level), nameof(NodeDto.Level)); + DefineMap(nameof(DataType.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(DataType.Path), nameof(NodeDto.Path)); + DefineMap(nameof(DataType.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(DataType.Name), nameof(NodeDto.Text)); + DefineMap(nameof(DataType.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(DataType.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(DataType.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(DataType.EditorAlias), nameof(DataTypeDto.EditorAlias)); + DefineMap(nameof(DataType.DatabaseType), nameof(DataTypeDto.DbType)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs index 0c2773c2dd..f6a0947bd1 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,16 +13,16 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IDictionaryItem))] public sealed class DictionaryMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public DictionaryMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.PrimaryKey); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.ItemKey, dto => dto.Key); - CacheMap(src => src.ParentId, dto => dto.Parent); + DefineMap(nameof(DictionaryItem.Id), nameof(DictionaryDto.PrimaryKey)); + DefineMap(nameof(DictionaryItem.Key), nameof(DictionaryDto.UniqueId)); + DefineMap(nameof(DictionaryItem.ItemKey), nameof(DictionaryDto.Key)); + DefineMap(nameof(DictionaryItem.ParentId), nameof(DictionaryDto.Parent)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs index 3f8641b959..672c8a4b0a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,16 +13,16 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IDictionaryTranslation))] public sealed class DictionaryTranslationMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public DictionaryTranslationMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.PrimaryKey); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.Language, dto => dto.LanguageId); - CacheMap(src => src.Value, dto => dto.Value); + DefineMap(nameof(DictionaryTranslation.Id), nameof(LanguageTextDto.PrimaryKey)); + DefineMap(nameof(DictionaryTranslation.Key), nameof(LanguageTextDto.UniqueId)); + DefineMap(nameof(DictionaryTranslation.Language), nameof(LanguageTextDto.LanguageId)); + DefineMap(nameof(DictionaryTranslation.Value), nameof(LanguageTextDto.Value)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs index 0bce8d7538..9106b76f54 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -8,16 +9,16 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(UmbracoDomain))] public sealed class DomainMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public DomainMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.RootContentId, dto => dto.RootStructureId); - CacheMap(src => src.LanguageId, dto => dto.DefaultLanguage); - CacheMap(src => src.DomainName, dto => dto.DomainName); + DefineMap(nameof(UmbracoDomain.Id), nameof(DomainDto.Id)); + DefineMap(nameof(UmbracoDomain.RootContentId), nameof(DomainDto.RootStructureId)); + DefineMap(nameof(UmbracoDomain.LanguageId), nameof(DomainDto.DefaultLanguage)); + DefineMap(nameof(UmbracoDomain.DomainName), nameof(DomainDto.DomainName)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs b/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs deleted file mode 100644 index ebf16a1d36..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Reflection; - -namespace Umbraco.Core.Persistence.Mappers -{ - internal class DtoMapModel - { - public DtoMapModel(Type type, PropertyInfo propertyInfo, string sourcePropertyName) - { - Type = type; - PropertyInfo = propertyInfo; - SourcePropertyName = sourcePropertyName; - } - - public string SourcePropertyName { get; private set; } - public Type Type { get; private set; } - public PropertyInfo PropertyInfo { get; private set; } - } -} diff --git a/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs index d3fb24273b..223689ec51 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models.Identity; using Umbraco.Core.Persistence.Dtos; @@ -8,25 +9,17 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IdentityUserLogin))] public sealed class ExternalLoginMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - public ExternalLoginMapper() + public ExternalLoginMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } + + protected override void DefineMaps() { - BuildMap(); + DefineMap(nameof(IdentityUserLogin.Id), nameof(ExternalLoginDto.Id)); + DefineMap(nameof(IdentityUserLogin.CreateDate), nameof(ExternalLoginDto.CreateDate)); + DefineMap(nameof(IdentityUserLogin.LoginProvider), nameof(ExternalLoginDto.LoginProvider)); + DefineMap(nameof(IdentityUserLogin.ProviderKey), nameof(ExternalLoginDto.ProviderKey)); + DefineMap(nameof(IdentityUserLogin.UserId), nameof(ExternalLoginDto.UserId)); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.LoginProvider, dto => dto.LoginProvider); - CacheMap(src => src.ProviderKey, dto => dto.ProviderKey); - CacheMap(src => src.UserId, dto => dto.UserId); - } - - #endregion } } diff --git a/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs b/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs index ea7d4c2f09..8941626b79 100644 --- a/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,15 +13,15 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(Language))] public sealed class LanguageMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public LanguageMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.IsoCode, dto => dto.IsoCode); - CacheMap(src => src.CultureName, dto => dto.CultureName); + DefineMap(nameof(Language.Id), nameof(LanguageDto.Id)); + DefineMap(nameof(Language.IsoCode), nameof(LanguageDto.IsoCode)); + DefineMap(nameof(Language.CultureName), nameof(LanguageDto.CultureName)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs index b6da1dc3d8..6a0d803052 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -8,22 +9,22 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IMacro))] internal sealed class MacroMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public MacroMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.CacheByPage, dto => dto.CacheByPage); - CacheMap(src => src.CacheByMember, dto => dto.CachePersonalized); - CacheMap(src => src.MacroType, dto => dto.MacroType); - CacheMap(src => src.DontRender, dto => dto.DontRender); - CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.CacheDuration, dto => dto.RefreshRate); - CacheMap(src => src.MacroSource, dto => dto.MacroSource); - CacheMap(src => src.UseInEditor, dto => dto.UseInEditor); + DefineMap(nameof(Macro.Id), nameof(MacroDto.Id)); + DefineMap(nameof(Macro.Alias), nameof(MacroDto.Alias)); + DefineMap(nameof(Macro.CacheByPage), nameof(MacroDto.CacheByPage)); + DefineMap(nameof(Macro.CacheByMember), nameof(MacroDto.CachePersonalized)); + DefineMap(nameof(Macro.MacroType), nameof(MacroDto.MacroType)); + DefineMap(nameof(Macro.DontRender), nameof(MacroDto.DontRender)); + DefineMap(nameof(Macro.Name), nameof(MacroDto.Name)); + DefineMap(nameof(Macro.CacheDuration), nameof(MacroDto.RefreshRate)); + DefineMap(nameof(Macro.MacroSource), nameof(MacroDto.MacroSource)); + DefineMap(nameof(Macro.UseInEditor), nameof(MacroDto.UseInEditor)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs index e20f7c1911..951b0cdf93 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs @@ -1,9 +1,14 @@ -using Umbraco.Core.Composing; +using System; +using System.Collections.Concurrent; +using Umbraco.Core.Composing; namespace Umbraco.Core.Persistence.Mappers { - public class MapperCollectionBuilder : LazyCollectionBuilderBase + public class MapperCollectionBuilder : SetCollectionBuilderBase { + private readonly ConcurrentDictionary> _maps + = new ConcurrentDictionary>(); + protected override MapperCollectionBuilder This => this; public override void RegisterWith(IRegister register) @@ -19,6 +24,11 @@ namespace Umbraco.Core.Persistence.Mappers register.Register(factory => factory.GetInstance()); } + protected override BaseMapper CreateItem(IFactory factory, Type itemType) + { + return (BaseMapper) factory.CreateInstance(itemType, _maps); + } + public MapperCollectionBuilder AddCoreMappers() { Add(); diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs index 776f929e0d..e2504594cd 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,29 +13,27 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(Umbraco.Core.Models.Media))] public sealed class MediaMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public MediaMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; + DefineMap(nameof(Models.Media.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(Models.Media.Key), nameof(NodeDto.UniqueId)); - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.Key, dto => dto.UniqueId); + DefineMap(nameof(Content.VersionId), nameof(ContentVersionDto.Id)); - CacheMap(src => src.VersionId, dto => dto.Id); - - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); + DefineMap(nameof(Models.Media.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(Models.Media.Level), nameof(NodeDto.Level)); + DefineMap(nameof(Models.Media.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(Models.Media.Path), nameof(NodeDto.Path)); + DefineMap(nameof(Models.Media.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(Models.Media.Name), nameof(NodeDto.Text)); + DefineMap(nameof(Models.Media.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(Models.Media.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(Models.Media.ContentTypeId), nameof(ContentDto.ContentTypeId)); + DefineMap(nameof(Models.Media.UpdateDate), nameof(ContentVersionDto.VersionDate)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs index 6cf83bc7aa..526a2da239 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,31 +13,29 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(MediaType))] public sealed class MediaTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public MediaTypeMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.IsContainer, dto => dto.IsContainer); - CacheMap(src => src.IsElement, dto => dto.IsElement); - CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + DefineMap(nameof(MediaType.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(MediaType.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(MediaType.Level), nameof(NodeDto.Level)); + DefineMap(nameof(MediaType.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(MediaType.Path), nameof(NodeDto.Path)); + DefineMap(nameof(MediaType.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(MediaType.Name), nameof(NodeDto.Text)); + DefineMap(nameof(MediaType.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(MediaType.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(MediaType.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(MediaType.Alias), nameof(ContentTypeDto.Alias)); + DefineMap(nameof(MediaType.AllowedAsRoot), nameof(ContentTypeDto.AllowAtRoot)); + DefineMap(nameof(MediaType.Description), nameof(ContentTypeDto.Description)); + DefineMap(nameof(MediaType.Icon), nameof(ContentTypeDto.Icon)); + DefineMap(nameof(MediaType.IsContainer), nameof(ContentTypeDto.IsContainer)); + DefineMap(nameof(MediaType.IsElement), nameof(ContentTypeDto.IsElement)); + DefineMap(nameof(MediaType.Thumbnail), nameof(ContentTypeDto.Thumbnail)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs index 0358b9e6d2..f462c449b5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -8,17 +9,17 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof (MemberGroup))] public sealed class MemberGroupMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public MemberGroupMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Key, dto => dto.UniqueId); + DefineMap(nameof(MemberGroup.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(MemberGroup.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(MemberGroup.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(MemberGroup.Name), nameof(NodeDto.Text)); + DefineMap(nameof(MemberGroup.Key), nameof(NodeDto.UniqueId)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs index a34d04fb2d..e0f29ce128 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; @@ -13,47 +14,47 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(Member))] public sealed class MemberMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public MemberMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => ((IUmbracoEntity)src).Level, dto => dto.Level); - CacheMap(src => ((IUmbracoEntity)src).ParentId, dto => dto.ParentId); - CacheMap(src => ((IUmbracoEntity)src).Path, dto => dto.Path); - CacheMap(src => ((IUmbracoEntity)src).SortOrder, dto => dto.SortOrder); - CacheMap(src => ((IUmbracoEntity)src).CreatorId, dto => dto.UserId); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); - CacheMap(src => src.ContentTypeAlias, dto => dto.Alias); - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); + DefineMap(nameof(Member.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(Member.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(Member.Level), nameof(NodeDto.Level)); + DefineMap(nameof(Member.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(Member.Path), nameof(NodeDto.Path)); + DefineMap(nameof(Member.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(Member.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(Member.Name), nameof(NodeDto.Text)); + DefineMap(nameof(Member.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(Member.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(Member.ContentTypeId), nameof(ContentDto.ContentTypeId)); + DefineMap(nameof(Member.ContentTypeAlias), nameof(ContentTypeDto.Alias)); + DefineMap(nameof(Member.UpdateDate), nameof(ContentVersionDto.VersionDate)); - CacheMap(src => src.Email, dto => dto.Email); - CacheMap(src => src.Username, dto => dto.LoginName); - CacheMap(src => src.RawPasswordValue, dto => dto.Password); + DefineMap(nameof(Member.Email), nameof(MemberDto.Email)); + DefineMap(nameof(Member.Username), nameof(MemberDto.LoginName)); + DefineMap(nameof(Member.RawPasswordValue), nameof(MemberDto.Password)); - CacheMap(src => src.IsApproved, dto => dto.IntegerValue); - CacheMap(src => src.IsLockedOut, dto => dto.IntegerValue); - CacheMap(src => src.Comments, dto => dto.TextValue); - CacheMap(src => src.RawPasswordAnswerValue, dto => dto.VarcharValue); - CacheMap(src => src.PasswordQuestion, dto => dto.VarcharValue); - CacheMap(src => src.FailedPasswordAttempts, dto => dto.IntegerValue); - CacheMap(src => src.LastLockoutDate, dto => dto.DateValue); - CacheMap(src => src.LastLoginDate, dto => dto.DateValue); - CacheMap(src => src.LastPasswordChangeDate, dto => dto.DateValue); + DefineMap(nameof(Member.IsApproved), nameof(PropertyDataDto.IntegerValue)); + DefineMap(nameof(Member.IsLockedOut), nameof(PropertyDataDto.IntegerValue)); + DefineMap(nameof(Member.Comments), nameof(PropertyDataDto.TextValue)); + DefineMap(nameof(Member.RawPasswordAnswerValue), nameof(PropertyDataDto.VarcharValue)); + DefineMap(nameof(Member.PasswordQuestion), nameof(PropertyDataDto.VarcharValue)); + DefineMap(nameof(Member.FailedPasswordAttempts), nameof(PropertyDataDto.IntegerValue)); + DefineMap(nameof(Member.LastLockoutDate), nameof(PropertyDataDto.DateValue)); + DefineMap(nameof(Member.LastLoginDate), nameof(PropertyDataDto.DateValue)); + DefineMap(nameof(Member.LastPasswordChangeDate), nameof(PropertyDataDto.DateValue)); /* Internal experiment */ - CacheMap(src => src.DateTimePropertyValue, dto => dto.DateValue); - CacheMap(src => src.IntegerPropertyValue, dto => dto.IntegerValue); - CacheMap(src => src.BoolPropertyValue, dto => dto.IntegerValue); - CacheMap(src => src.LongStringPropertyValue, dto => dto.TextValue); - CacheMap(src => src.ShortStringPropertyValue, dto => dto.VarcharValue); - CacheMap(src => src.PropertyTypeAlias, dto => dto.Alias); + DefineMap(nameof(Member.DateTimePropertyValue), nameof(PropertyDataDto.DateValue)); + DefineMap(nameof(Member.IntegerPropertyValue), nameof(PropertyDataDto.IntegerValue)); + DefineMap(nameof(Member.BoolPropertyValue), nameof(PropertyDataDto.IntegerValue)); + DefineMap(nameof(Member.LongStringPropertyValue), nameof(PropertyDataDto.TextValue)); + DefineMap(nameof(Member.ShortStringPropertyValue), nameof(PropertyDataDto.VarcharValue)); + DefineMap(nameof(Member.PropertyTypeAlias), nameof(PropertyTypeDto.Alias)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs index 9a4e4ec040..7a6d54c499 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,31 +13,29 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof (IMemberType))] public sealed class MemberTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public MemberTypeMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.IsContainer, dto => dto.IsContainer); - CacheMap(src => src.IsElement, dto => dto.IsElement); - CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + DefineMap(nameof(MemberType.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(MemberType.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(MemberType.Level), nameof(NodeDto.Level)); + DefineMap(nameof(MemberType.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(MemberType.Path), nameof(NodeDto.Path)); + DefineMap(nameof(MemberType.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(MemberType.Name), nameof(NodeDto.Text)); + DefineMap(nameof(MemberType.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(MemberType.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(MemberType.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(MemberType.Alias), nameof(ContentTypeDto.Alias)); + DefineMap(nameof(MemberType.AllowedAsRoot), nameof(ContentTypeDto.AllowAtRoot)); + DefineMap(nameof(MemberType.Description), nameof(ContentTypeDto.Description)); + DefineMap(nameof(MemberType.Icon), nameof(ContentTypeDto.Icon)); + DefineMap(nameof(MemberType.IsContainer), nameof(ContentTypeDto.IsContainer)); + DefineMap(nameof(MemberType.IsElement), nameof(ContentTypeDto.IsElement)); + DefineMap(nameof(MemberType.Thumbnail), nameof(ContentTypeDto.Thumbnail)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs index b485a2d420..19b5859bcd 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -11,16 +12,16 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(PropertyGroup))] public sealed class PropertyGroupMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public PropertyGroupMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); + DefineMap(nameof(PropertyGroup.Id), nameof(PropertyTypeGroupDto.Id)); + DefineMap(nameof(PropertyGroup.Key), nameof(PropertyTypeGroupDto.UniqueId)); + DefineMap(nameof(PropertyGroup.SortOrder), nameof(PropertyTypeGroupDto.SortOrder)); + DefineMap(nameof(PropertyGroup.Name), nameof(PropertyTypeGroupDto.Text)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs index 2df9bf9af5..354f4a2622 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -7,14 +8,14 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(Property))] public sealed class PropertyMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public PropertyMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.PropertyTypeId, dto => dto.PropertyTypeId); + DefineMap(nameof(Property.Id), nameof(PropertyDataDto.Id)); + DefineMap(nameof(Property.PropertyTypeId), nameof(PropertyDataDto.PropertyTypeId)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index 66c1d75165..ab1869a7f5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -11,25 +12,23 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(PropertyType))] public sealed class PropertyTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public PropertyTypeMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.DataTypeId, dto => dto.DataTypeId); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Mandatory, dto => dto.Mandatory); - CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.ValidationRegExp, dto => dto.ValidationRegExp); - CacheMap(src => src.PropertyEditorAlias, dto => dto.EditorAlias); - CacheMap(src => src.ValueStorageType, dto => dto.DbType); + DefineMap(nameof(PropertyType.Key), nameof(PropertyTypeDto.UniqueId)); + DefineMap(nameof(PropertyType.Id), nameof(PropertyTypeDto.Id)); + DefineMap(nameof(PropertyType.Alias), nameof(PropertyTypeDto.Alias)); + DefineMap(nameof(PropertyType.DataTypeId), nameof(PropertyTypeDto.DataTypeId)); + DefineMap(nameof(PropertyType.Description), nameof(PropertyTypeDto.Description)); + DefineMap(nameof(PropertyType.Mandatory), nameof(PropertyTypeDto.Mandatory)); + DefineMap(nameof(PropertyType.Name), nameof(PropertyTypeDto.Name)); + DefineMap(nameof(PropertyType.SortOrder), nameof(PropertyTypeDto.SortOrder)); + DefineMap(nameof(PropertyType.ValidationRegExp), nameof(PropertyTypeDto.ValidationRegExp)); + DefineMap(nameof(PropertyType.PropertyEditorAlias), nameof(DataTypeDto.EditorAlias)); + DefineMap(nameof(PropertyType.ValueStorageType), nameof(DataTypeDto.DbType)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs index 83d9eb4220..c7af941d27 100644 --- a/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,18 +13,18 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(Relation))] public sealed class RelationMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public RelationMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.ChildId, dto => dto.ChildId); - CacheMap(src => src.Comment, dto => dto.Comment); - CacheMap(src => src.CreateDate, dto => dto.Datetime); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.RelationTypeId, dto => dto.RelationType); + DefineMap(nameof(Relation.Id), nameof(RelationDto.Id)); + DefineMap(nameof(Relation.ChildId), nameof(RelationDto.ChildId)); + DefineMap(nameof(Relation.Comment), nameof(RelationDto.Comment)); + DefineMap(nameof(Relation.CreateDate), nameof(RelationDto.Datetime)); + DefineMap(nameof(Relation.ParentId), nameof(RelationDto.ParentId)); + DefineMap(nameof(Relation.RelationTypeId), nameof(RelationDto.RelationType)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs index b9bd3d0e89..366a804aa6 100644 --- a/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,18 +13,18 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IRelationType))] public sealed class RelationTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public RelationTypeMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.ChildObjectType, dto => dto.ChildObjectType); - CacheMap(src => src.IsBidirectional, dto => dto.Dual); - CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.ParentObjectType, dto => dto.ParentObjectType); + DefineMap(nameof(RelationType.Id), nameof(RelationTypeDto.Id)); + DefineMap(nameof(RelationType.Alias), nameof(RelationTypeDto.Alias)); + DefineMap(nameof(RelationType.ChildObjectType), nameof(RelationTypeDto.ChildObjectType)); + DefineMap(nameof(RelationType.IsBidirectional), nameof(RelationTypeDto.Dual)); + DefineMap(nameof(RelationType.Name), nameof(RelationTypeDto.Name)); + DefineMap(nameof(RelationType.ParentObjectType), nameof(RelationTypeDto.ParentObjectType)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs index 0c723fced6..9f3d557e9c 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -8,19 +9,19 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(IServerRegistration))] internal sealed class ServerRegistrationMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public ServerRegistrationMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.IsActive, dto => dto.IsActive); - CacheMap(src => src.IsMaster, dto => dto.IsMaster); - CacheMap(src => src.ServerAddress, dto => dto.ServerAddress); - CacheMap(src => src.CreateDate, dto => dto.DateRegistered); - CacheMap(src => src.UpdateDate, dto => dto.DateAccessed); - CacheMap(src => src.ServerIdentity, dto => dto.ServerIdentity); + DefineMap(nameof(ServerRegistration.Id), nameof(ServerRegistrationDto.Id)); + DefineMap(nameof(ServerRegistration.IsActive), nameof(ServerRegistrationDto.IsActive)); + DefineMap(nameof(ServerRegistration.IsMaster), nameof(ServerRegistrationDto.IsMaster)); + DefineMap(nameof(ServerRegistration.ServerAddress), nameof(ServerRegistrationDto.ServerAddress)); + DefineMap(nameof(ServerRegistration.CreateDate), nameof(ServerRegistrationDto.DateRegistered)); + DefineMap(nameof(ServerRegistration.UpdateDate), nameof(ServerRegistrationDto.DateAccessed)); + DefineMap(nameof(ServerRegistration.ServerIdentity), nameof(ServerRegistrationDto.ServerIdentity)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/SimpleContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/SimpleContentTypeMapper.cs index 3ad975defb..f98ff3b694 100644 --- a/src/Umbraco.Core/Persistence/Mappers/SimpleContentTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/SimpleContentTypeMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -8,31 +9,29 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(SimpleContentType))] public sealed class SimpleContentTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public SimpleContentTypeMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.IsContainer, dto => dto.IsContainer); - CacheMap(src => src.IsElement, dto => dto.IsElement); - CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + DefineMap(nameof(ContentType.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(ContentType.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(ContentType.Level), nameof(NodeDto.Level)); + DefineMap(nameof(ContentType.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(ContentType.Path), nameof(NodeDto.Path)); + DefineMap(nameof(ContentType.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(ContentType.Name), nameof(NodeDto.Text)); + DefineMap(nameof(ContentType.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(ContentType.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(ContentType.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(ContentType.Alias), nameof(ContentTypeDto.Alias)); + DefineMap(nameof(ContentType.AllowedAsRoot), nameof(ContentTypeDto.AllowAtRoot)); + DefineMap(nameof(ContentType.Description), nameof(ContentTypeDto.Description)); + DefineMap(nameof(ContentType.Icon), nameof(ContentTypeDto.Icon)); + DefineMap(nameof(ContentType.IsContainer), nameof(ContentTypeDto.IsContainer)); + DefineMap(nameof(ContentType.IsElement), nameof(ContentTypeDto.IsElement)); + DefineMap(nameof(ContentType.Thumbnail), nameof(ContentTypeDto.Thumbnail)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs index 63f73d060a..88849e0444 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,18 +13,16 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(ITag))] public sealed class TagMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public TagMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Text, dto => dto.Text); - CacheMap(src => src.Group, dto => dto.Group); - CacheMap(src => src.LanguageId, dto => dto.LanguageId); + DefineMap(nameof(Tag.Id), nameof(TagDto.Id)); + DefineMap(nameof(Tag.Text), nameof(TagDto.Text)); + DefineMap(nameof(Tag.Group), nameof(TagDto.Group)); + DefineMap(nameof(Tag.LanguageId), nameof(TagDto.LanguageId)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs index ca5faab134..b9defdee6a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -12,18 +13,16 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(ITemplate))] public sealed class TemplateMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public TemplateMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.MasterTemplateId, dto => dto.ParentId); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.Alias, dto => dto.Alias); + DefineMap(nameof(Template.Id), nameof(TemplateDto.NodeId)); + DefineMap(nameof(Template.MasterTemplateId), nameof(NodeDto.ParentId)); + DefineMap(nameof(Template.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(Template.Alias), nameof(TemplateDto.Alias)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index 556af9b88a..16e2e8bcd5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; @@ -7,22 +8,22 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof (IUmbracoEntity))] public sealed class UmbracoEntityMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public UmbracoEntityMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); + DefineMap(nameof(IUmbracoEntity.Id), nameof(NodeDto.NodeId)); + DefineMap(nameof(IUmbracoEntity.CreateDate), nameof(NodeDto.CreateDate)); + DefineMap(nameof(IUmbracoEntity.Level), nameof(NodeDto.Level)); + DefineMap(nameof(IUmbracoEntity.ParentId), nameof(NodeDto.ParentId)); + DefineMap(nameof(IUmbracoEntity.Path), nameof(NodeDto.Path)); + DefineMap(nameof(IUmbracoEntity.SortOrder), nameof(NodeDto.SortOrder)); + DefineMap(nameof(IUmbracoEntity.Name), nameof(NodeDto.Text)); + DefineMap(nameof(IUmbracoEntity.Trashed), nameof(NodeDto.Trashed)); + DefineMap(nameof(IUmbracoEntity.Key), nameof(NodeDto.UniqueId)); + DefineMap(nameof(IUmbracoEntity.CreatorId), nameof(NodeDto.UserId)); } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs index 92e2abd03e..75d34e4081 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Dtos; @@ -12,29 +13,18 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(UserGroup))] public sealed class UserGroupMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public UserGroupMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it - // otherwise that would fail because there is no public constructor. - public UserGroupMapper() + protected override void DefineMaps() { - BuildMap(); + DefineMap(nameof(UserGroup.Id), nameof(UserGroupDto.Id)); + DefineMap(nameof(UserGroup.Alias), nameof(UserGroupDto.Alias)); + DefineMap(nameof(UserGroup.Name), nameof(UserGroupDto.Name)); + DefineMap(nameof(UserGroup.Icon), nameof(UserGroupDto.Icon)); + DefineMap(nameof(UserGroup.StartContentId), nameof(UserGroupDto.StartContentId)); + DefineMap(nameof(UserGroup.StartMediaId), nameof(UserGroupDto.StartMediaId)); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.StartContentId, dto => dto.StartContentId); - CacheMap(src => src.StartMediaId, dto => dto.StartMediaId); - } - - #endregion } } diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index d32f48910d..4f92b5c224 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Dtos; @@ -8,28 +9,28 @@ namespace Umbraco.Core.Persistence.Mappers [MapperFor(typeof(User))] public sealed class UserMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public UserMapper(Lazy sqlContext, ConcurrentDictionary> maps) + : base(sqlContext, maps) + { } - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() + protected override void DefineMaps() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Email, dto => dto.Email); - CacheMap(src => src.Username, dto => dto.Login); - CacheMap(src => src.RawPasswordValue, dto => dto.Password); - CacheMap(src => src.Name, dto => dto.UserName); + DefineMap(nameof(User.Id), nameof(UserDto.Id)); + DefineMap(nameof(User.Email), nameof(UserDto.Email)); + DefineMap(nameof(User.Username), nameof(UserDto.Login)); + DefineMap(nameof(User.RawPasswordValue), nameof(UserDto.Password)); + DefineMap(nameof(User.Name), nameof(UserDto.UserName)); //NOTE: This column in the db is *not* used! - //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); - CacheMap(src => src.IsApproved, dto => dto.Disabled); - CacheMap(src => src.IsLockedOut, dto => dto.NoConsole); - CacheMap(src => src.Language, dto => dto.UserLanguage); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); - CacheMap(src => src.LastLockoutDate, dto => dto.LastLockoutDate); - CacheMap(src => src.LastLoginDate, dto => dto.LastLoginDate); - CacheMap(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate); - CacheMap(src => src.SecurityStamp, dto => dto.SecurityStampToken); + //DefineMap(nameof(User.DefaultPermissions), nameof(UserDto.DefaultPermissions)); + DefineMap(nameof(User.IsApproved), nameof(UserDto.Disabled)); + DefineMap(nameof(User.IsLockedOut), nameof(UserDto.NoConsole)); + DefineMap(nameof(User.Language), nameof(UserDto.UserLanguage)); + DefineMap(nameof(User.CreateDate), nameof(UserDto.CreateDate)); + DefineMap(nameof(User.UpdateDate), nameof(UserDto.UpdateDate)); + DefineMap(nameof(User.LastLockoutDate), nameof(UserDto.LastLockoutDate)); + DefineMap(nameof(User.LastLoginDate), nameof(UserDto.LastLoginDate)); + DefineMap(nameof(User.LastPasswordChangeDate), nameof(UserDto.LastPasswordChangeDate)); + DefineMap(nameof(User.SecurityStamp), nameof(UserDto.SecurityStampToken)); } } } diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index bf26523d9e..03d82a345f 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Querying //don't execute if compiled if (Visited == false) { - var field = _mapper.Map(SqlSyntax, m.Member.Name, true); + var field = _mapper.Map(m.Member.Name); if (field.IsNullOrWhiteSpace()) throw new InvalidOperationException($"The mapper returned an empty field for the member name: {m.Member.Name} for type: {m.Expression.Type}."); return field; @@ -48,7 +48,7 @@ namespace Umbraco.Core.Persistence.Querying //don't execute if compiled if (Visited == false) { - var field = _mapper.Map(SqlSyntax, m.Member.Name, true); + var field = _mapper.Map(m.Member.Name); if (field.IsNullOrWhiteSpace()) throw new InvalidOperationException($"The mapper returned an empty field for the member name: {m.Member.Name} for type: {m.Expression.Type}."); return field; @@ -70,7 +70,7 @@ namespace Umbraco.Core.Persistence.Querying if (Visited == false) { var subMapper = _mappers[m.Expression.Type]; // throws if not found - var field = subMapper.Map(SqlSyntax, m.Member.Name, true); + var field = subMapper.Map(m.Member.Name); if (field.IsNullOrWhiteSpace()) throw new InvalidOperationException($"The mapper returned an empty field for the member name: {m.Member.Name} for type: {m.Expression.Type}"); return field; diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs new file mode 100644 index 0000000000..e7657ac941 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + // TODO + // this should be IContentTypeRepository, and what is IContentTypeRepository at the moment should + // become IDocumentTypeRepository - but since these interfaces are public, that would be breaking + + /// + /// Represents the content types common repository, dealing with document, media and member types. + /// + public interface IContentTypeCommonRepository + { + /// + /// Gets and cache all types. + /// + IEnumerable GetAllTypes(); + + /// + /// Clears the cache. + /// + void ClearCache(); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs index 09059089e7..fbaff4f510 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs @@ -1,16 +1,8 @@ using System.Collections.Generic; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { public interface IMediaTypeRepository : IContentTypeRepositoryBase - { - /// - /// Gets all entities of the specified query - /// - /// - /// An enumerable list of objects - IEnumerable GetByQuery(IQuery query); - } + { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 821f0941fc..6b8e6515f5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -292,7 +292,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (psql.Arguments[i] is string s && s == "[[[ISOCODE]]]") { psql.Arguments[i] = ordering.Culture; - break; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs new file mode 100644 index 0000000000..194a00b7f2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + /// + /// Implements . + /// + internal class ContentTypeCommonRepository : IContentTypeCommonRepository + { + private const string CacheKey = "Umbraco.Core.Persistence.Repositories.Implement.ContentTypeCommonRepository::AllTypes"; + + private readonly AppCaches _appCaches; + private readonly IScopeAccessor _scopeAccessor; + private readonly ITemplateRepository _templateRepository; + + /// + /// Initializes a new instance of the class. + /// + public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateRepository templateRepository, AppCaches appCaches) + { + _scopeAccessor = scopeAccessor; + _templateRepository = templateRepository; + _appCaches = appCaches; + } + + private IScope AmbientScope => _scopeAccessor.AmbientScope; + private IUmbracoDatabase Database => AmbientScope.Database; + private ISqlContext SqlContext => AmbientScope.SqlContext; + private Sql Sql() => SqlContext.Sql(); + //private Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args); + //private ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax; + //private IQuery Query() => SqlContext.Query(); + + /// + public IEnumerable GetAllTypes() + { + // use a 5 minutes sliding cache - same as FullDataSet cache policy + return _appCaches.RuntimeCache.GetCacheItem(CacheKey, GetAllTypesInternal, TimeSpan.FromMinutes(5), true); + } + + /// + public void ClearCache() + { + _appCaches.RuntimeCache.Clear(CacheKey); + } + + private IEnumerable GetAllTypesInternal() + { + var contentTypes = new Dictionary(); + + // get content types + var sql1 = Sql() + .Select(r => r.Select(x => x.NodeDto)) + .From() + .InnerJoin().On((ct, n) => ct.NodeId == n.NodeId) + .OrderBy(x => x.NodeId); + + var contentTypeDtos = Database.Fetch(sql1); + + // get allowed content types + var sql2 = Sql() + .Select() + .From() + .OrderBy(x => x.Id); + + var allowedDtos = Database.Fetch(sql2); + + // prepare + // note: same alias could be used for media, content... but always different ids = ok + var aliases = contentTypeDtos.ToDictionary(x => x.NodeId, x => x.Alias); + + // create + var allowedDtoIx = 0; + foreach (var contentTypeDto in contentTypeDtos) + { + // create content type + IContentTypeComposition contentType; + if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MediaType) + contentType = ContentTypeFactory.BuildMediaTypeEntity(contentTypeDto); + else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.DocumentType) + contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto); + else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType) + contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto); + else throw new Exception("panic"); + contentTypes.Add(contentType.Id, contentType); + + // map allowed content types + var allowedContentTypes = new List(); + while (allowedDtoIx < allowedDtos.Count && allowedDtos[allowedDtoIx].Id == contentTypeDto.NodeId) + { + var allowedDto = allowedDtos[allowedDtoIx]; + if (!aliases.TryGetValue(allowedDto.AllowedId, out var alias)) continue; + allowedContentTypes.Add(new ContentTypeSort(new Lazy(() => allowedDto.AllowedId), allowedDto.SortOrder, alias)); + allowedDtoIx++; + } + contentType.AllowedContentTypes = allowedContentTypes; + } + + MapTemplates(contentTypes); + MapComposition(contentTypes); + MapGroupsAndProperties(contentTypes); + + // finalize + foreach (var contentType in contentTypes.Values) + { + contentType.ResetDirtyProperties(false); + } + + return contentTypes.Values; + } + + private void MapTemplates(Dictionary contentTypes) + { + // get templates + var sql1 = Sql() + .Select() + .From() + .OrderBy(x => x.ContentTypeNodeId); + + var templateDtos = Database.Fetch(sql1); + //var templates = templateRepository.GetMany(templateDtos.Select(x => x.TemplateNodeId).ToArray()).ToDictionary(x => x.Id, x => x); + var templates = _templateRepository.GetMany().ToDictionary(x => x.Id, x => x); + var templateDtoIx = 0; + + foreach (var c in contentTypes.Values) + { + if (!(c is ContentType contentType)) continue; + + // map allowed templates + var allowedTemplates = new List(); + var defaultTemplateId = 0; + while (templateDtoIx < templateDtos.Count && templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id) + { + var allowedDto = templateDtos[templateDtoIx]; + if (!templates.TryGetValue(allowedDto.TemplateNodeId, out var template)) continue; + allowedTemplates.Add(template); + templateDtoIx++; + + if (allowedDto.IsDefault) + defaultTemplateId = template.Id; + } + contentType.AllowedTemplates = allowedTemplates; + contentType.DefaultTemplateId = defaultTemplateId; + } + } + + private void MapComposition(IDictionary contentTypes) + { + // get parent/child + var sql1 = Sql() + .Select() + .From() + .OrderBy(x => x.ChildId); + + var compositionDtos = Database.Fetch(sql1); + + // map + var compositionIx = 0; + foreach (var contentType in contentTypes.Values) + { + while (compositionIx < compositionDtos.Count && compositionDtos[compositionIx].ChildId == contentType.Id) + { + var parentDto = compositionDtos[compositionIx]; + if (!contentTypes.TryGetValue(parentDto.ParentId, out var parentContentType)) continue; + contentType.AddContentType(parentContentType); + compositionIx++; + } + } + } + + private void MapGroupsAndProperties(IDictionary contentTypes) + { + var sql1 = Sql() + .Select() + .From() + .InnerJoin().On((ptg, ct) => ptg.ContentTypeNodeId == ct.NodeId) + .OrderBy(x => x.NodeId) + .AndBy(x => x.SortOrder, x => x.Id); + + var groupDtos = Database.Fetch(sql1); + + var sql2 = Sql() + .Select(r => r.Select(x => x.DataTypeDto)) + .AndSelect() + .From() + .InnerJoin().On((pt, dt) => pt.DataTypeId == dt.NodeId) + .InnerJoin().On((pt, ct) => pt.ContentTypeId == ct.NodeId) + .LeftJoin().On((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id) + .LeftJoin().On((pt, mpt) => pt.Id == mpt.PropertyTypeId) + .OrderBy(x => x.NodeId) + .AndBy(x => x.SortOrder, x => x.Id) // NULLs will come first or last, never mind, we deal with it below + .AndBy(x => x.SortOrder, x => x.Id); + + var propertyDtos = Database.Fetch(sql2); + var builtinProperties = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + + var groupIx = 0; + var propertyIx = 0; + foreach (var contentType in contentTypes.Values) + { + // only IContentType is publishing + var isPublishing = contentType is IContentType; + + // get group-less properties (in case NULL is ordered first) + var noGroupPropertyTypes = new PropertyTypeCollection(isPublishing); + while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null) + { + noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties)); + propertyIx++; + } + + // get groups and their properties + var groupCollection = new PropertyGroupCollection(); + while (groupIx < groupDtos.Count && groupDtos[groupIx].ContentTypeNodeId == contentType.Id) + { + var group = MapPropertyGroup(groupDtos[groupIx], isPublishing); + groupCollection.Add(group); + groupIx++; + + while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == group.Id) + { + group.PropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties)); + propertyIx++; + } + } + contentType.PropertyGroups = groupCollection; + + // get group-less properties (in case NULL is ordered last) + while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null) + { + noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties)); + propertyIx++; + } + contentType.NoGroupPropertyTypes = noGroupPropertyTypes; + } + } + + private PropertyGroup MapPropertyGroup(PropertyTypeGroupDto dto, bool isPublishing) + { + return new PropertyGroup(new PropertyTypeCollection(isPublishing)) + { + Id = dto.Id, + Name = dto.Text, + SortOrder = dto.SortOrder, + Key = dto.UniqueId + }; + } + + private PropertyType MapPropertyType(IContentTypeComposition contentType, PropertyTypeCommonDto dto, IDictionary builtinProperties) + { + var groupId = dto.PropertyTypeGroupId; + + var readonlyStorageType = builtinProperties.TryGetValue(dto.Alias, out var propertyType); + var storageType = readonlyStorageType ? propertyType.ValueStorageType : Enum.Parse(dto.DataTypeDto.DbType); + + if (contentType is MemberType memberType) + { + var access = new MemberTypePropertyProfileAccess(dto.ViewOnProfile, dto.CanEdit, dto.IsSensitive); + memberType.MemberTypePropertyTypes.Add(dto.Alias, access); + } + + return new PropertyType(dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, dto.Alias) + { + Description = dto.Description, + DataTypeId = dto.DataTypeId, + Id = dto.Id, + Key = dto.UniqueId, + Mandatory = dto.Mandatory, + Name = dto.Name, + PropertyGroupId = groupId.HasValue ? new Lazy(() => groupId.Value) : null, + SortOrder = dto.SortOrder, + ValidationRegExp = dto.ValidationRegExp, + Variations = (ContentVariation)dto.Variations + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 095c77eb42..98ddcdcb17 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -17,13 +17,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository { - private readonly ITemplateRepository _templateRepository; - - public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, ITemplateRepository templateRepository) - : base(scopeAccessor, cache, logger) - { - _templateRepository = templateRepository; - } + public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) + : base(scopeAccessor, cache, logger, commonRepository) + { } protected override bool SupportsPublishing => ContentType.SupportsPublishingConst; @@ -32,75 +28,81 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ true); } + // every GetExists method goes cachePolicy.GetSomething which in turns goes PerformGetAll, + // since this is a FullDataSet policy - and everything is cached + // so here, + // every PerformGet/Exists just GetMany() and then filters + // except PerformGetAll which is the one really doing the job + + // TODO: the filtering is highly inefficient as we deep-clone everything + // there should be a way to GetMany(predicate) right from the cache policy! + // and ah, well, this all caching should be refactored + the cache refreshers + // should to repository.Clear() not deal with magic caches by themselves + protected override IContentType PerformGet(int id) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Id == id); - } + => GetMany().FirstOrDefault(x => x.Id == id); protected override IContentType PerformGet(Guid id) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Key == id); - } + => GetMany().FirstOrDefault(x => x.Key == id); protected override IContentType PerformGet(string alias) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } + => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); protected override bool PerformExists(Guid id) - { - return GetMany().FirstOrDefault(x => x.Key == id) != null; - } + => GetMany().FirstOrDefault(x => x.Key == id) != null; protected override IEnumerable PerformGetAll(params int[] ids) { - if (ids.Any()) - { - //NOTE: This logic should never be executed according to our cache policy - return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository) - .Where(x => ids.Contains(x.Id)); - } - - return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository); + // the cache policy will always want everything + // even GetMany(ids) gets everything and filters afterwards + if (ids.Any()) throw new Exception("panic"); + return CommonRepository.GetAllTypes().OfType(); } protected override IEnumerable PerformGetAll(params Guid[] ids) { - // use the underlying GetAll which will force cache all content types - return ids.Any() ? GetMany().Where(x => ids.Contains(x.Key)) : GetMany(); + var all = GetMany(); + return ids.Any() ? all.Where(x => ids.Contains(x.Key)) : all; } protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); + var baseQuery = GetBaseQuery(false); + var translator = new SqlTranslator(baseQuery, query); var sql = translator.Translate(); + var ids = Database.Fetch(sql).Distinct().ToArray(); - var dtos = Database.Fetch(sql); - - return - //This returns a lookup from the GetAll cached lookup - (dtos.Any() - ? GetMany(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.NodeId).ToArray()) - : Enumerable.Empty()) - //order the result by name - .OrderBy(x => x.Name); + return ids.Length > 0 ? GetMany(ids).OrderBy(x => x.Name) : Enumerable.Empty(); } - /// - /// Gets all entities of the specified query - /// - /// - /// An enumerable list of objects + /// public IEnumerable GetByQuery(IQuery query) { var ints = PerformGetByQuery(query).ToArray(); - return ints.Any() - ? GetMany(ints) - : Enumerable.Empty(); + return ints.Length > 0 ? GetMany(ints) : Enumerable.Empty(); + } + + protected IEnumerable PerformGetByQuery(IQuery query) + { + // used by DataTypeService to remove properties + // from content types if they have a deleted data type - see + // notes in DataTypeService.Delete as it's a bit weird + + var sqlClause = Sql() + .SelectAll() + .From() + .RightJoin() + .On(left => left.Id, right => right.PropertyTypeGroupId) + .InnerJoin() + .On(left => left.DataTypeId, right => right.NodeId); + + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .OrderBy(x => x.PropertyTypeGroupId); + + return Database + .FetchOneToMany(x => x.PropertyTypeDtos, sql) + .Select(x => x.ContentTypeNodeId).Distinct(); } /// @@ -141,7 +143,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (aliases.Length == 0) return Enumerable.Empty(); var sql = Sql() - .Select("cmsContentType.nodeId") + .Select(x => x.NodeId) .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.NodeId) @@ -156,14 +158,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql = isCount ? sql.SelectCount() - : sql.Select(r => r.Select(x => x.ContentTypeDto, r1 => r1.Select(x => x.NodeDto))); + : sql.Select(x => x.NodeId); sql .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .LeftJoin() - .On(left => left.ContentTypeNodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index e9b952f11d..591fa2b660 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -10,11 +10,9 @@ using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Core.Services; @@ -28,9 +26,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { - protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) + protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) : base(scopeAccessor, cache, logger) - { } + { + CommonRepository = commonRepository; + } + + protected IContentTypeCommonRepository CommonRepository { get; } protected abstract bool SupportsPublishing { get; } @@ -61,7 +63,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // move to parent (or -1), update path, save moving.ParentId = parentId; var movingPath = moving.Path + ","; // save before changing - moving.Path = (container == null ? Constants.System.Root.ToString() : container.Path) + "," + moving.Id; + moving.Path = (container == null ? Constants.System.RootString : container.Path) + "," + moving.Id; moving.Level = container == null ? 1 : container.Level + 1; Save(moving); @@ -82,38 +84,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return moveInfo; } - /// - /// Returns the content type ids that match the query - /// - /// - /// - protected IEnumerable PerformGetByQuery(IQuery query) + + protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType storageType, string propertyTypeAlias) { - // used by DataTypeDefinitionRepository to remove properties - // from content types if they have a deleted data type - see - // notes in DataTypeDefinitionRepository.Delete as it's a bit - // weird - - var sqlClause = Sql() - .SelectAll() - .From() - .RightJoin() - .On(left => left.Id, right => right.PropertyTypeGroupId) - .InnerJoin() - .On(left => left.DataTypeId, right => right.NodeId); - - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .OrderBy(x => x.PropertyTypeGroupId); - - return Database - .FetchOneToMany(x => x.PropertyTypeDtos, sql) - .Select(x => x.ContentTypeNodeId).Distinct(); + return new PropertyType(propertyEditorAlias, storageType, propertyTypeAlias); } - protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType dbType, string propertyTypeAlias) + protected override void PersistDeletedItem(TEntity entity) { - return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias); + base.PersistDeletedItem(entity); + CommonRepository.ClearCache(); // always } protected void PersistNewBaseContentType(IContentTypeComposition entity) @@ -228,6 +208,8 @@ AND umbracoNode.nodeObjectType = @objectType", propertyType.PropertyEditorAlias = dataTypeDto.EditorAlias; propertyType.ValueStorageType = dataTypeDto.DbType.EnumParse(true); } + + CommonRepository.ClearCache(); // always } protected void PersistUpdatedBaseContentType(IContentTypeComposition entity) @@ -532,6 +514,8 @@ AND umbracoNode.id <> @id", if (orphanPropertyTypeIds != null) foreach (var id in orphanPropertyTypeIds) DeletePropertyType(entity.Id, id); + + CommonRepository.ClearCache(); // always } private IEnumerable GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable all) @@ -992,74 +976,6 @@ AND umbracoNode.id <> @id", new { Id = contentTypeId, PropertyTypeId = propertyTypeId }); } - protected IEnumerable GetAllowedContentTypeIds(int id) - { - var sql = Sql() - .SelectAll() - .From() - .LeftJoin() - .On(left => left.AllowedId, right => right.NodeId) - .Where(x => x.Id == id); - - var allowedContentTypeDtos = Database.Fetch(sql); - return allowedContentTypeDtos.Select(x => new ContentTypeSort(new Lazy(() => x.AllowedId), x.SortOrder, x.ContentTypeDto.Alias)).ToList(); - } - - protected PropertyGroupCollection GetPropertyGroupCollection(int id, DateTime createDate, DateTime updateDate) - { - var sql = Sql() - .SelectAll() - .From() - .LeftJoin() - .On(left => left.Id, right => right.PropertyTypeGroupId) - .LeftJoin() - .On(left => left.DataTypeId, right => right.NodeId) - .Where(x => x.ContentTypeNodeId == id) - .OrderBy(x => x.Id); - - - var dtos = Database - .Fetch(sql); - - var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, SupportsPublishing, id, createDate, updateDate,CreatePropertyType); - - return new PropertyGroupCollection(propertyGroups); - } - - protected PropertyTypeCollection GetPropertyTypeCollection(int id, DateTime createDate, DateTime updateDate) - { - var sql = Sql() - .SelectAll() - .From() - .InnerJoin() - .On(left => left.DataTypeId, right => right.NodeId) - .Where(x => x.ContentTypeId == id); - - var dtos = Database.Fetch(sql); - - // TODO: Move this to a PropertyTypeFactory - var list = new List(); - foreach (var dto in dtos.Where(x => x.PropertyTypeGroupId <= 0)) - { - var propType = CreatePropertyType(dto.DataTypeDto.EditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias); - propType.DataTypeId = dto.DataTypeId; - propType.Description = dto.Description; - propType.Id = dto.Id; - propType.Key = dto.UniqueId; - propType.Name = dto.Name; - propType.Mandatory = dto.Mandatory; - propType.SortOrder = dto.SortOrder; - propType.ValidationRegExp = dto.ValidationRegExp; - propType.CreateDate = createDate; - propType.UpdateDate = updateDate; - list.Add(propType); - } - //Reset dirty properties - Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false)); - - return new PropertyTypeCollection(SupportsPublishing, list); - } - protected void ValidateAlias(PropertyType pt) { if (string.IsNullOrWhiteSpace(pt.Alias)) @@ -1114,589 +1030,6 @@ AND umbracoNode.id <> @id", } } - internal static class ContentTypeQueryMapper - { - public class AssociatedTemplate - { - public AssociatedTemplate(int templateId, string alias, string templateName) - { - TemplateId = templateId; - Alias = alias; - TemplateName = templateName; - } - - public int TemplateId { get; set; } - public string Alias { get; set; } - public string TemplateName { get; set; } - - protected bool Equals(AssociatedTemplate other) - { - return TemplateId == other.TemplateId; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((AssociatedTemplate)obj); - } - - public override int GetHashCode() - { - return TemplateId; - } - } - - public static IEnumerable GetMediaTypes( - IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing, - TRepo contentTypeRepository) - where TRepo : IReadRepository - { - IDictionary> allParentMediaTypeIds; - var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds) - .ToArray(); - - MapContentTypeChildren(mediaTypes, db, sqlSyntax, isPublishing, contentTypeRepository, allParentMediaTypeIds); - - return mediaTypes; - } - - public static IEnumerable GetContentTypes( - IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing, - TRepo contentTypeRepository, - ITemplateRepository templateRepository) - where TRepo : IReadRepository - { - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; - var contentTypes = MapContentTypes(db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) - .ToArray(); - - if (contentTypes.Any()) - { - MapContentTypeTemplates( - contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates); - - MapContentTypeChildren(contentTypes, db, sqlSyntax, isPublishing, contentTypeRepository, allParentContentTypeIds); - } - - return contentTypes; - } - - internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, - IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing, - TRepo contentTypeRepository, - IDictionary> allParentContentTypeIds) - where TRepo : IReadRepository - { - //NOTE: SQL call #2 - - var ids = contentTypes.Select(x => x.Id).ToArray(); - IDictionary allPropGroups; - IDictionary allPropTypes; - MapGroupsAndProperties(ids, db, sqlSyntax, isPublishing, out allPropTypes, out allPropGroups); - - foreach (var contentType in contentTypes) - { - contentType.PropertyGroups = allPropGroups[contentType.Id]; - contentType.NoGroupPropertyTypes = allPropTypes[contentType.Id]; - } - - //NOTE: SQL call #3++ - - if (allParentContentTypeIds != null) - { - var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray(); - if (allParentIdsAsArray.Any()) - { - var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); - - foreach (var contentType in contentTypes) - { - var entityId = contentType.Id; - - var parentContentTypes = allParentContentTypes.Where(x => - { - var parentEntityId = x.Id; - - return allParentContentTypeIds[entityId].Contains(parentEntityId); - }); - foreach (var parentContentType in parentContentTypes) - { - var result = contentType.AddContentType(parentContentType); - //Do something if adding fails? (Should hopefully not be possible unless someone created a circular reference) - } - - // reset dirty initial properties (U4-1946) - ((EntityBase)contentType).ResetDirtyProperties(false); - } - } - } - - - } - - internal static void MapContentTypeTemplates(IContentType[] contentTypes, - IDatabase db, - TRepo contentTypeRepository, - ITemplateRepository templateRepository, - IDictionary> associatedTemplates) - where TRepo : IReadRepository - { - if (associatedTemplates == null || associatedTemplates.Any() == false) return; - - //NOTE: SQL call #3++ - //SEE: http://issues.umbraco.org/issue/U4-5174 to fix this - - var templateIds = associatedTemplates.SelectMany(x => x.Value).Select(x => x.TemplateId) - .Distinct() - .ToArray(); - - var templates = (templateIds.Any() - ? templateRepository.GetMany(templateIds) - : Enumerable.Empty()).ToArray(); - - foreach (var contentType in contentTypes) - { - var entityId = contentType.Id; - - var associatedTemplateIds = associatedTemplates[entityId].Select(x => x.TemplateId) - .Distinct() - .ToArray(); - - contentType.AllowedTemplates = (associatedTemplateIds.Any() - ? templates.Where(x => associatedTemplateIds.Contains(x.Id)) - : Enumerable.Empty()).ToArray(); - } - - - } - - internal static IEnumerable MapMediaTypes(IDatabase db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> parentMediaTypeIds) - { - if (db == null) throw new ArgumentNullException(nameof(db)); - - var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations, - cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, - umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, - umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, - umbracoNode.uniqueID as nUniqueId - FROM cmsContentType - INNER JOIN umbracoNode - ON cmsContentType.nodeId = umbracoNode.id - LEFT JOIN ( - SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder - FROM cmsContentTypeAllowedContentType - INNER JOIN cmsContentType - ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId - ) AllowedTypes - ON AllowedTypes.Id = cmsContentType.nodeId - LEFT JOIN ( - SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId - FROM cmsContentType2ContentType - INNER JOIN umbracoNode - ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" - ) ParentTypes - ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType) - ORDER BY ctId"; - - var result = db.Fetch(sql, new { nodeObjectType = Constants.ObjectTypes.MediaType }); - - if (result.Any() == false) - { - parentMediaTypeIds = null; - return Enumerable.Empty(); - } - - parentMediaTypeIds = new Dictionary>(); - var mappedMediaTypes = new List(); - - //loop through each result and fill in our required values, each row will contain different required data than the rest. - // it is much quicker to iterate each result and populate instead of looking up the values over and over in the result like - // we used to do. - var queue = new Queue(result); - var currAllowedContentTypes = new List(); - - while (queue.Count > 0) - { - var ct = queue.Dequeue(); - - //check for allowed content types - int? allowedCtId = ct.ctaAllowedId; - int? allowedCtSort = ct.ctaSortOrder; - string allowedCtAlias = ct.ctaAlias; - if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) - { - var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); - if (currAllowedContentTypes.Contains(ctSort) == false) - { - currAllowedContentTypes.Add(ctSort); - } - } - - //always ensure there's a list for this content type - if (parentMediaTypeIds.ContainsKey(ct.ctId) == false) - parentMediaTypeIds[ct.ctId] = new List(); - - //check for parent ids and assign to the outgoing collection - int? parentId = ct.chtParentId; - if (parentId.HasValue) - { - var associatedParentIds = parentMediaTypeIds[ct.ctId]; - if (associatedParentIds.Contains(parentId.Value) == false) - associatedParentIds.Add(parentId.Value); - } - - if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) - { - //it's the last in the queue or the content type is changing (moving to the next one) - var mediaType = CreateForMapping(ct, currAllowedContentTypes); - mappedMediaTypes.Add(mediaType); - - //Here we need to reset the current variables, we're now collecting data for a different content type - currAllowedContentTypes = new List(); - } - } - - return mappedMediaTypes; - } - - private static IMediaType CreateForMapping(dynamic currCt, List currAllowedContentTypes) - { - // * create the DTO object - // * create the content type object - // * map the allowed content types - // * add to the outgoing list - - var contentTypeDto = new ContentTypeDto - { - Alias = currCt.ctAlias, - AllowAtRoot = currCt.ctAllowAtRoot, - Description = currCt.ctDesc, - Icon = currCt.ctIcon, - IsContainer = currCt.ctIsContainer, - IsElement = currCt.ctIsElement, - NodeId = currCt.ctId, - PrimaryKey = currCt.ctPk, - Thumbnail = currCt.ctThumb, - Variations = (byte) currCt.ctVariations, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = currCt.nCreateDate, - Level = (short)currCt.nLevel, - NodeId = currCt.ctId, - NodeObjectType = currCt.nObjectType, - ParentId = currCt.nParentId, - Path = currCt.nPath, - SortOrder = currCt.nSortOrder, - Text = currCt.nName, - Trashed = currCt.nTrashed, - UniqueId = currCt.nUniqueId, - UserId = currCt.nUser - } - }; - - //now create the content type object; - var mediaType = ContentTypeFactory.BuildMediaTypeEntity(contentTypeDto); - - //map the allowed content types - mediaType.AllowedContentTypes = currAllowedContentTypes; - - return mediaType; - } - - internal static IEnumerable MapContentTypes(IDatabase db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> associatedTemplates, - out IDictionary> parentContentTypeIds) - { - if (db == null) throw new ArgumentNullException(nameof(db)); - - var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, - cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations, - cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, - umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, - umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, - umbracoNode.uniqueID as nUniqueId, - Template.alias as tAlias, Template.nodeId as tId,Template.text as tText - FROM cmsContentType - INNER JOIN umbracoNode - ON cmsContentType.nodeId = umbracoNode.id - LEFT JOIN cmsDocumentType - ON cmsDocumentType.contentTypeNodeId = cmsContentType.nodeId - LEFT JOIN ( - SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder - FROM cmsContentTypeAllowedContentType - INNER JOIN cmsContentType - ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId - ) AllowedTypes - ON AllowedTypes.Id = cmsContentType.nodeId - LEFT JOIN ( - SELECT * FROM cmsTemplate - INNER JOIN umbracoNode - ON cmsTemplate.nodeId = umbracoNode.id - ) as Template - ON Template.nodeId = cmsDocumentType.templateNodeId - LEFT JOIN ( - SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId - FROM cmsContentType2ContentType - INNER JOIN umbracoNode - ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" - ) ParentTypes - ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType) - ORDER BY ctId"; - - var result = db.Fetch(sql, new { nodeObjectType = Constants.ObjectTypes.DocumentType }); - - if (result.Any() == false) - { - parentContentTypeIds = null; - associatedTemplates = null; - return Enumerable.Empty(); - } - - parentContentTypeIds = new Dictionary>(); - associatedTemplates = new Dictionary>(); - var mappedContentTypes = new List(); - - var queue = new Queue(result); - var currDefaultTemplate = -1; - var currAllowedContentTypes = new List(); - while (queue.Count > 0) - { - var ct = queue.Dequeue(); - - //check for default templates - bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); - int? templateId = ct.dtTemplateId; - if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue) - { - currDefaultTemplate = templateId.Value; - } - - //always ensure there's a list for this content type - if (associatedTemplates.ContainsKey(ct.ctId) == false) - associatedTemplates[ct.ctId] = new List(); - - //check for associated templates and assign to the outgoing collection - if (ct.tId != null) - { - var associatedTemplate = new AssociatedTemplate(ct.tId, ct.tAlias, ct.tText); - var associatedList = associatedTemplates[ct.ctId]; - - if (associatedList.Contains(associatedTemplate) == false) - associatedList.Add(associatedTemplate); - } - - //check for allowed content types - int? allowedCtId = ct.ctaAllowedId; - int? allowedCtSort = ct.ctaSortOrder; - string allowedCtAlias = ct.ctaAlias; - if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) - { - var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); - if (currAllowedContentTypes.Contains(ctSort) == false) - { - currAllowedContentTypes.Add(ctSort); - } - } - - //always ensure there's a list for this content type - if (parentContentTypeIds.ContainsKey(ct.ctId) == false) - parentContentTypeIds[ct.ctId] = new List(); - - //check for parent ids and assign to the outgoing collection - int? parentId = ct.chtParentId; - if (parentId.HasValue) - { - var associatedParentIds = parentContentTypeIds[ct.ctId]; - - if (associatedParentIds.Contains(parentId.Value) == false) - associatedParentIds.Add(parentId.Value); - } - - if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) - { - //it's the last in the queue or the content type is changing (moving to the next one) - var contentType = CreateForMapping(ct, currAllowedContentTypes, currDefaultTemplate); - mappedContentTypes.Add(contentType); - - //Here we need to reset the current variables, we're now collecting data for a different content type - currDefaultTemplate = -1; - currAllowedContentTypes = new List(); - } - } - - return mappedContentTypes; - } - - private static IContentType CreateForMapping(dynamic currCt, List currAllowedContentTypes, int currDefaultTemplate) - { - // * set the default template to the first one if a default isn't found - // * create the DTO object - // * create the content type object - // * map the allowed content types - // * add to the outgoing list - - var dtDto = new ContentTypeTemplateDto - { - //create the content type dto - ContentTypeDto = new ContentTypeDto - { - Alias = currCt.ctAlias, - AllowAtRoot = currCt.ctAllowAtRoot, - Description = currCt.ctDesc, - Icon = currCt.ctIcon, - IsContainer = currCt.ctIsContainer, - IsElement = currCt.ctIsElement, - NodeId = currCt.ctId, - PrimaryKey = currCt.ctPk, - Thumbnail = currCt.ctThumb, - Variations = (byte) currCt.ctVariations, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = currCt.nCreateDate, - Level = (short)currCt.nLevel, - NodeId = currCt.ctId, - NodeObjectType = currCt.nObjectType, - ParentId = currCt.nParentId, - Path = currCt.nPath, - SortOrder = currCt.nSortOrder, - Text = currCt.nName, - Trashed = currCt.nTrashed, - UniqueId = currCt.nUniqueId, - UserId = currCt.nUser - } - }, - ContentTypeNodeId = currCt.ctId, - IsDefault = currDefaultTemplate != -1, - TemplateNodeId = currDefaultTemplate != -1 ? currDefaultTemplate : 0, - }; - - //now create the content type object - var contentType = ContentTypeFactory.BuildContentTypeEntity(dtDto.ContentTypeDto); - - // NOTE - // that was done by the factory but makes little sense, moved here, so - // now we have to reset dirty props again (as the factory does it) and yet, - // we are not managing allowed templates... the whole thing is weird. - ((ContentType)contentType).DefaultTemplateId = dtDto.TemplateNodeId; - contentType.ResetDirtyProperties(false); - - //map the allowed content types - contentType.AllowedContentTypes = currAllowedContentTypes; - - return contentType; - } - - internal static void MapGroupsAndProperties(int[] contentTypeIds, IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing, - out IDictionary allPropertyTypeCollection, - out IDictionary allPropertyGroupCollection) - { - allPropertyGroupCollection = new Dictionary(); - allPropertyTypeCollection = new Dictionary(); - - // query below is not safe + pointless if array is empty - if (contentTypeIds.Length == 0) return; - - var sqlGroups = @"SELECT - pg.contenttypeNodeId AS contentTypeId, - pg.id AS id, pg.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @", - pg.sortOrder AS sortOrder, pg." + sqlSyntax.GetQuotedColumnName("text") + @" AS text -FROM cmsPropertyTypeGroup pg -WHERE pg.contenttypeNodeId IN (@ids) -ORDER BY contentTypeId, id"; - - var sqlProps = @"SELECT - pt.contentTypeId AS contentTypeId, - pt.id AS id, pt.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @", - pt.propertyTypeGroupId AS groupId, - pt.Alias AS alias, pt." + sqlSyntax.GetQuotedColumnName("Description") + @" AS " + sqlSyntax.GetQuotedColumnName("desc") + $@", pt.mandatory AS mandatory, - pt.Name AS name, pt.sortOrder AS sortOrder, pt.validationRegExp AS regexp, pt.variations as variations, - dt.nodeId as dataTypeId, dt.dbType as dbType, dt.propertyEditorAlias as editorAlias -FROM cmsPropertyType pt -INNER JOIN {Constants.DatabaseSchema.Tables.DataType} as dt ON pt.dataTypeId = dt.nodeId -WHERE pt.contentTypeId IN (@ids) -ORDER BY contentTypeId, groupId, id"; - - if (contentTypeIds.Length > 2000) - throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); - - var groups = db.Fetch(sqlGroups, new { ids = contentTypeIds }); - var groupsEnumerator = groups.GetEnumerator(); - var group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null; - - var props = db.Fetch(sqlProps, new { ids = contentTypeIds }); - var propsEnumerator = props.GetEnumerator(); - var prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null; - - // groups are ordered by content type, group id - // props are ordered by content type, group id, prop id - - foreach (var contentTypeId in contentTypeIds) - { - var propertyTypeCollection = allPropertyTypeCollection[contentTypeId] = new PropertyTypeCollection(isPublishing); - var propertyGroupCollection = allPropertyGroupCollection[contentTypeId] = new PropertyGroupCollection(); - - while (prop != null && prop.contentTypeId == contentTypeId && prop.groupId == null) - { - AddPropertyType(propertyTypeCollection, prop); - prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null; - } - - while (group != null && group.contentTypeId == contentTypeId) - { - var propertyGroup = new PropertyGroup(new PropertyTypeCollection(isPublishing)) - { - Id = group.id, - Name = group.text, - SortOrder = group.sortOrder, - Key = group.key - }; - propertyGroupCollection.Add(propertyGroup); - - while (prop != null && prop.groupId == group.id) - { - AddPropertyType(propertyGroup.PropertyTypes, prop, propertyGroup); - prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null; - } - - group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null; - } - } - - propsEnumerator.Dispose(); - groupsEnumerator.Dispose(); - } - - private static void AddPropertyType(PropertyTypeCollection propertyTypes, dynamic prop, PropertyGroup propertyGroup = null) - { - var propertyType = new PropertyType(prop.editorAlias, Enum.Parse(prop.dbType), prop.alias) - { - Description = prop.desc, - DataTypeId = prop.dataTypeId, - Id = prop.id, - Key = prop.key, - Mandatory = Convert.ToBoolean(prop.mandatory), - Name = prop.name, - PropertyGroupId = propertyGroup == null ? null : new Lazy(() => propertyGroup.Id), - SortOrder = prop.sortOrder, - ValidationRegExp = prop.regexp, - Variations = (ContentVariation) prop.variations - }; - propertyTypes.Add(propertyType); - } - } - protected abstract TEntity PerformGet(Guid id); protected abstract TEntity PerformGet(string alias); protected abstract IEnumerable PerformGetAll(params Guid[] ids); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 24b50d294b..6c08e05995 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -232,7 +232,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .OrderByDescending(x => x.Current) .AndByDescending(x => x.VersionDate); - return MapDtosToContent(Database.Fetch(sql), true, true); + return MapDtosToContent(Database.Fetch(sql), true, true).Skip(skip).Take(take); } public override IContent GetVersion(int versionId) @@ -982,8 +982,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // invariant: left join will yield NULL and we must use pcv to determine published // variant: left join may yield NULL or something, and that determines published + var joins = Sql() - .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype"); + .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype") + // left join on optional culture variation + //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code + .LeftJoin(nested => + nested.InnerJoin("langp").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccvp", "langp"), "ccvp") + .On((version, ccv) => version.Id == ccv.VersionId, aliasLeft: "pcv", aliasRight: "ccvp"); sql = InsertJoins(sql, joins); @@ -993,7 +999,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // when invariant, ie 'variations' does not have the culture flag (value 1), use the global 'published' flag on pcv.id, // otherwise check if there's a version culture variation for the lang, via ccv.id - ", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccv.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important! + ", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccvp.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important! sql = Sql(sqlText, sql.Arguments); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 7cb78f4c9f..0fa48e5521 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IIdentityUserLogin PerformGet(int id) { var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.Where(GetBaseWhereClause(), new { id = id }); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs new file mode 100644 index 0000000000..b48b5588de --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + internal static class LanguageRepositoryExtensions + { + public static bool IsDefault(this ILanguageRepository repo, string culture) + { + if (culture == null || culture == "*") return false; + return repo.GetDefaultIsoCode().InvariantEquals(culture); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index a6f39bdb71..281255e755 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -16,8 +16,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MediaTypeRepository : ContentTypeRepositoryBase, IMediaTypeRepository { - public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) + : base(scopeAccessor, cache, logger, commonRepository) { } protected override bool SupportsPublishing => MediaType.SupportsPublishingConst; @@ -27,83 +27,46 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ true); } + // every GetExists method goes cachePolicy.GetSomething which in turns goes PerformGetAll, + // since this is a FullDataSet policy - and everything is cached + // so here, + // every PerformGet/Exists just GetMany() and then filters + // except PerformGetAll which is the one really doing the job + protected override IMediaType PerformGet(int id) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Id == id); - } + => GetMany().FirstOrDefault(x => x.Id == id); protected override IMediaType PerformGet(Guid id) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Key == id); - } + => GetMany().FirstOrDefault(x => x.Key == id); protected override bool PerformExists(Guid id) - { - return GetMany().FirstOrDefault(x => x.Key == id) != null; - } + => GetMany().FirstOrDefault(x => x.Key == id) != null; protected override IMediaType PerformGet(string alias) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } + => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); protected override IEnumerable PerformGetAll(params int[] ids) { - if (ids.Any()) - { - //NOTE: This logic should never be executed according to our cache policy - return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this) - .Where(x => ids.Contains(x.Id)); - } - - return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this); + // the cache policy will always want everything + // even GetMany(ids) gets everything and filters afterwards + if (ids.Any()) throw new Exception("panic"); + return CommonRepository.GetAllTypes().OfType(); } protected override IEnumerable PerformGetAll(params Guid[] ids) { - //use the underlying GetAll which will force cache all content types - - if (ids.Any()) - { - return GetMany().Where(x => ids.Contains(x.Key)); - } - else - { - return GetMany(); - } + var all = GetMany(); + return ids.Any() ? all.Where(x => ids.Contains(x.Key)) : all; } protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); + var baseQuery = GetBaseQuery(false); + var translator = new SqlTranslator(baseQuery, query); var sql = translator.Translate(); + var ids = Database.Fetch(sql).Distinct().ToArray(); - var dtos = Database.Fetch(sql); - - return - //This returns a lookup from the GetAll cached lookup - (dtos.Any() - ? GetMany(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) - : Enumerable.Empty()) - //order the result by name - .OrderBy(x => x.Name); - } - - /// - /// Gets all entities of the specified query - /// - /// - /// An enumerable list of objects - public IEnumerable GetByQuery(IQuery query) - { - var ints = PerformGetByQuery(query).ToArray(); - return ints.Any() - ? GetMany(ints) - : Enumerable.Empty(); + return ids.Length > 0 ? GetMany(ids).OrderBy(x => x.Name) : Enumerable.Empty(); } protected override Sql GetBaseQuery(bool isCount) @@ -112,12 +75,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql = isCount ? sql.SelectCount() - : sql.Select(r => r.Select(x => x.NodeDto)); + : sql.Select(x => x.NodeId); sql .From() - .InnerJoin() - .On( left => left.NodeId, right => right.NodeId) + .InnerJoin().On( left => left.NodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index afb6ac8b43..a32ec1b422 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -17,8 +17,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MemberTypeRepository : ContentTypeRepositoryBase, IMemberTypeRepository { - public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) + : base(scopeAccessor, cache, logger, commonRepository) { } protected override bool SupportsPublishing => MemberType.SupportsPublishingConst; @@ -28,108 +28,49 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ true); } + // every GetExists method goes cachePolicy.GetSomething which in turns goes PerformGetAll, + // since this is a FullDataSet policy - and everything is cached + // so here, + // every PerformGet/Exists just GetMany() and then filters + // except PerformGetAll which is the one really doing the job + protected override IMemberType PerformGet(int id) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Id == id); - } + => GetMany().FirstOrDefault(x => x.Id == id); protected override IMemberType PerformGet(Guid id) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Key == id); - } + => GetMany().FirstOrDefault(x => x.Key == id); protected override IEnumerable PerformGetAll(params Guid[] ids) { - //use the underlying GetAll which will force cache all content types - - if (ids.Any()) - { - return GetMany().Where(x => ids.Contains(x.Key)); - } - else - { - return GetMany(); - } + var all = GetMany(); + return ids.Any() ? all.Where(x => ids.Contains(x.Key)) : all; } protected override bool PerformExists(Guid id) - { - return GetMany().FirstOrDefault(x => x.Key == id) != null; - } + => GetMany().FirstOrDefault(x => x.Key == id) != null; protected override IMemberType PerformGet(string alias) - { - //use the underlying GetAll which will force cache all content types - return GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } + => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - //NOTE: This logic should never be executed according to our cache policy - var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.id='{0}'", x))); - sql.Where(statement); - } - sql.OrderByDescending(x => x.NodeId); - - var dtos = Database - .Fetch(sql) // cannot use FetchOneToMany because we have 2 collections! - .Transform(MapOneToManies) - .ToList(); - - return BuildFromDtos(dtos); + // the cache policy will always want everything + // even GetMany(ids) gets everything and filters afterwards + if (ids.Any()) throw new Exception("panic"); + return CommonRepository.GetAllTypes().OfType(); } protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlSubquery = GetSubquery(); - var translator = new SqlTranslator(sqlSubquery, query); - var subquery = translator.Translate(); + var subQuery = GetSubquery(); + var translator = new SqlTranslator(subQuery, query); + var subSql = translator.Translate(); var sql = GetBaseQuery(false) - .Append("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments) + .WhereIn(x => x.NodeId, subSql) .OrderBy(x => x.SortOrder); + var ids = Database.Fetch(sql).Distinct().ToArray(); - var dtos = Database - .Fetch(sql) // cannot use FetchOneToMany because we have 2 collections! - .Transform(MapOneToManies) - .ToList(); - - return BuildFromDtos(dtos); - } - - private IEnumerable MapOneToManies(IEnumerable dtos) - { - MemberTypeReadOnlyDto acc = null; - foreach (var dto in dtos) - { - if (acc == null) - { - acc = dto; - } - else if (acc.UniqueId == dto.UniqueId) - { - var prop = dto.PropertyTypes.SingleOrDefault(); - var group = dto.PropertyTypeGroups.SingleOrDefault(); - - if (prop != null && prop.Id.HasValue && acc.PropertyTypes.Any(x => x.Id == prop.Id.Value) == false) - acc.PropertyTypes.Add(prop); - - if (group != null && group.Id.HasValue && acc.PropertyTypeGroups.Any(x => x.Id == group.Id.Value) == false) - acc.PropertyTypeGroups.Add(group); - } - else - { - yield return acc; - acc = dto; - } - } - - if (acc != null) - yield return acc; + return ids.Length > 0 ? GetMany(ids).OrderBy(x => x.Name) : Enumerable.Empty(); } protected override Sql GetBaseQuery(bool isCount) @@ -144,18 +85,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } var sql = Sql() - .Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", - "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID", - "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", - "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", - "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsMemberType.isSensitive", - $"{Constants.DatabaseSchema.Tables.DataType}.propertyEditorAlias", $"{Constants.DatabaseSchema.Tables.DataType}.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", - "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID", - "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") + .Select(x => x.NodeId) .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) - .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) .LeftJoin().On(left => left.NodeId, right => right.DataTypeId) .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectTypeId); @@ -170,7 +104,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) - .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) .LeftJoin().On(left => left.NodeId, right => right.DataTypeId) .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectTypeId); @@ -212,12 +146,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { entity.AddPropertyType(standardPropertyType.Value, Constants.Conventions.Member.StandardPropertiesGroupName); } - + EnsureExplicitDataTypeForBuiltInProperties(entity); PersistNewBaseContentType(entity); //Handles the MemberTypeDto (cmsMemberType table) - var memberTypeDtos = ContentTypeFactory.BuildMemberTypeDtos(entity); + var memberTypeDtos = ContentTypeFactory.BuildMemberPropertyTypeDtos(entity); foreach (var memberTypeDto in memberTypeDtos) { Database.Insert(memberTypeDto); @@ -245,13 +179,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); entity.SortOrder = maxSortOrder + 1; } - + EnsureExplicitDataTypeForBuiltInProperties(entity); PersistUpdatedBaseContentType(entity); // remove and insert - handle cmsMemberType table - Database.Delete("WHERE NodeId = @Id", new { Id = entity.Id }); - var memberTypeDtos = ContentTypeFactory.BuildMemberTypeDtos(entity); + Database.Delete("WHERE NodeId = @Id", new { Id = entity.Id }); + var memberTypeDtos = ContentTypeFactory.BuildMemberPropertyTypeDtos(entity); foreach (var memberTypeDto in memberTypeDtos) { Database.Insert(memberTypeDto); @@ -264,19 +198,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// Override so we can specify explicit db type's on any property types that are built-in. /// /// - /// + /// /// /// - protected override PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType dbType, string propertyTypeAlias) + protected override PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType storageType, string propertyTypeAlias) { //custom property type constructor logic to set explicit dbtype's for built in properties - var stdProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); - var propDbType = GetDbTypeForBuiltInProperty(propertyTypeAlias, dbType, stdProps); - return new PropertyType(propertyEditorAlias, propDbType.Result, - //This flag tells the property type that it has an explicit dbtype and that it cannot be changed - // which is what we want for the built-in properties. - propDbType.Success, - propertyTypeAlias); + var builtinProperties = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + var readonlyStorageType = builtinProperties.TryGetValue(propertyTypeAlias, out var propertyType); + storageType = readonlyStorageType ? propertyType.ValueStorageType : storageType; + return new PropertyType(propertyEditorAlias, storageType, readonlyStorageType, propertyTypeAlias); } /// @@ -286,62 +217,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// private static void EnsureExplicitDataTypeForBuiltInProperties(IContentTypeBase memberType) { - var stdProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + var builtinProperties = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); foreach (var propertyType in memberType.PropertyTypes) { - var dbTypeAttempt = GetDbTypeForBuiltInProperty(propertyType.Alias, propertyType.ValueStorageType, stdProps); - if (dbTypeAttempt) + if (builtinProperties.ContainsKey(propertyType.Alias)) { - //this reset's it's current data type reference which will be re-assigned based on the property editor assigned on the next line + //this reset's its current data type reference which will be re-assigned based on the property editor assigned on the next line propertyType.DataTypeId = 0; } } } - - /// - /// Builds a collection of entities from a collection of Dtos - /// - /// - /// - private IEnumerable BuildFromDtos(List dtos) - { - if (dtos == null || dtos.Any() == false) - return Enumerable.Empty(); - - return dtos.Select(x => - { - bool needsSaving; - var memberType = MemberTypeReadOnlyFactory.BuildEntity(x, out needsSaving); - if (needsSaving) PersistUpdatedItem(memberType); - return memberType; - }).ToList(); - } - - /// - /// If this is one of our internal properties - we will manually assign the data type since they must - /// always correspond to the correct db type no matter what the backing data type is assigned. - /// - /// - /// - /// - /// - /// Successful attempt if it was a built in property - /// - internal static Attempt GetDbTypeForBuiltInProperty( - string propAlias, - ValueStorageType dbType, - Dictionary standardProps) - { - var aliases = standardProps.Select(x => x.Key).ToArray(); - - //check if it is built in - if (aliases.Contains(propAlias)) - { - //return the pre-determined db type for this property - return Attempt.Succeed(standardProps.Single(x => x.Key == propAlias).Value.ValueStorageType); - } - - return Attempt.Fail(dbType); - } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs index a7e366a94f..69e4db5940 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs @@ -78,51 +78,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } - // TODO: but now that we have 1 unique repository? - // this is a *bad* idea because PerformCount captures the current repository and its UOW - // - //private static RepositoryCachePolicyOptions _defaultOptions; - //protected virtual RepositoryCachePolicyOptions DefaultOptions - //{ - // get - // { - // return _defaultOptions ?? (_defaultOptions - // = new RepositoryCachePolicyOptions(() => - // { - // // get count of all entities of current type (TEntity) to ensure cached result is correct - // // create query once if it is needed (no need for locking here) - query is static! - // var query = _hasIdQuery ?? (_hasIdQuery = Query.Builder.Where(x => x.Id != 0)); - // return PerformCount(query); - // })); - // } - //} - + // ReSharper disable once StaticMemberInGenericType + private static RepositoryCachePolicyOptions _defaultOptions; + // ReSharper disable once InconsistentNaming protected virtual RepositoryCachePolicyOptions DefaultOptions { get { - return new RepositoryCachePolicyOptions(() => - { - // get count of all entities of current type (TEntity) to ensure cached result is correct - // create query once if it is needed (no need for locking here) - query is static! - var query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); - return PerformCount(query); - }); + return _defaultOptions ?? (_defaultOptions + = new RepositoryCachePolicyOptions(() => + { + // get count of all entities of current type (TEntity) to ensure cached result is correct + // create query once if it is needed (no need for locking here) - query is static! + var query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); + return PerformCount(query); + })); } } - // this would be better for perfs BUT it breaks the tests - l8tr - // - //private static IRepositoryCachePolicy _defaultCachePolicy; - //protected virtual IRepositoryCachePolicy DefaultCachePolicy - //{ - // get - // { - // return _defaultCachePolicy ?? (_defaultCachePolicy - // = new DefaultRepositoryCachePolicy(IsolatedCache, DefaultOptions)); - // } - //} - protected IRepositoryCachePolicy CachePolicy { get diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 9e0999e38f..9027e9269c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -822,7 +822,7 @@ ORDER BY colName"; var expressionMember = ExpressionHelper.GetMemberInfo(orderBy); var mapper = _mapperCollection[typeof(IUser)]; - var mappedField = mapper.Map(SqlContext.SqlSyntax, expressionMember.Name); + var mappedField = mapper.Map(expressionMember.Name); if (mappedField.IsNullOrWhiteSpace()) throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause"); diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index 23c01e7768..dc86ff060c 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -119,9 +119,23 @@ namespace Umbraco.Core.Persistence } /// - public bool CanConnect => Configured && DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName); + public bool CanConnect + { + get + { + if (!Configured || !DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) return false; - private DatabaseType DetectSqlServerVersion() + if (_serverVersionDetected) return true; + + if (_databaseType.IsSqlServer()) + DetectSqlServerVersion(); + _serverVersionDetected = true; + + return true; + } + } + + private void DetectSqlServerVersion() { // replace NPoco database type by a more efficient one @@ -138,28 +152,22 @@ namespace Umbraco.Core.Persistence fromSettings = true; } - DatabaseType databaseType; switch (versionName) { case SqlServerSyntaxProvider.VersionName.V2008: - databaseType = DatabaseType.SqlServer2008; + _databaseType = DatabaseType.SqlServer2008; break; case SqlServerSyntaxProvider.VersionName.V2012: case SqlServerSyntaxProvider.VersionName.V2014: case SqlServerSyntaxProvider.VersionName.V2016: case SqlServerSyntaxProvider.VersionName.V2017: - databaseType = DatabaseType.SqlServer2012; + _databaseType = DatabaseType.SqlServer2012; break; // else leave unchanged - default: - databaseType = _databaseType; - break; } _logger.Debug("SqlServer {SqlServerVersion}, DatabaseType is {DatabaseType} ({Source}).", - versionName, databaseType, fromSettings ? "settings" : "detected"); - - return databaseType; + versionName, _databaseType, fromSettings ? "settings" : "detected"); } /// @@ -216,9 +224,6 @@ namespace Umbraco.Core.Persistence if (_npocoDatabaseFactory == null) throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); - if (_databaseType.IsSqlServer() && DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) - _databaseType = DetectSqlServerVersion(); - SqlContext = new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); _logger.Debug("Configured."); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index 09170b746a..8ad09733f8 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -36,6 +36,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (source is int) return (int)source == 1; + // this is required for correct true/false handling in nested content elements + if (source is long) + return (long)source == 1; + if (source is bool) return (bool)source; diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreInitialComponent.cs similarity index 51% rename from src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs rename to src/Umbraco.Core/Runtime/CoreInitialComponent.cs index e9842a1ba1..2e40bf1339 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComponent.cs @@ -1,28 +1,12 @@ -using System.Collections.Generic; -using AutoMapper; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; using Umbraco.Core.IO; namespace Umbraco.Core.Runtime { - public class CoreRuntimeComponent : IComponent + public class CoreInitialComponent : IComponent { - private readonly IEnumerable _mapperProfiles; - - public CoreRuntimeComponent(IEnumerable mapperProfiles) - { - _mapperProfiles = mapperProfiles; - } - public void Initialize() { - // mapper profiles have been registered & are created by the container - Mapper.Initialize(configuration => - { - foreach (var profile in _mapperProfiles) - configuration.AddProfile(profile); - }); - // ensure we have some essential directories // every other component can then initialize safely IOHelper.EnsurePathExists("~/App_Data"); diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs similarity index 96% rename from src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs rename to src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 690ac4d56c..f32a46f3f8 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -24,7 +24,9 @@ using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidato namespace Umbraco.Core.Runtime { - public class CoreRuntimeComposer : ComponentComposer, IRuntimeComposer + // core's initial composer composes before all core composers + [ComposeBefore(typeof(ICoreComposer))] + public class CoreInitialComposer : ComponentComposer { public override void Compose(Composition composition) { diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 7462ecdf67..f9a41b4f66 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Threading; using System.Web; +using System.Web.Hosting; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; @@ -12,10 +10,8 @@ using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; -using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Services.Implement; using Umbraco.Core.Sync; namespace Umbraco.Core.Runtime @@ -70,15 +66,19 @@ namespace Umbraco.Core.Runtime // objects. using (var timer = profilingLogger.TraceDuration( - $"Booting Umbraco {UmbracoVersion.SemanticVersion.ToSemanticString()} on {NetworkHelper.MachineName}.", + $"Booting Umbraco {UmbracoVersion.SemanticVersion.ToSemanticString()}.", "Booted.", "Boot failed.")) { + logger.Info("Booting site '{HostingSiteName}', app '{HostingApplicationID}', path '{HostingPhysicalPath}', server '{MachineName}'.", + HostingEnvironment.SiteName, + HostingEnvironment.ApplicationID, + HostingEnvironment.ApplicationPhysicalPath, + NetworkHelper.MachineName); logger.Debug("Runtime: {Runtime}", GetType().FullName); // application environment ConfigureUnhandledException(); - ConfigureAssemblyResolve(); ConfigureApplicationRootPath(); Boot(register, timer); @@ -168,7 +168,7 @@ namespace Umbraco.Core.Runtime _state.BootFailedException = bfe; } - timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves + timer?.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves // if something goes wrong above, we may end up with no factory // meaning nothing can get the runtime state, etc - so let's try @@ -211,20 +211,6 @@ namespace Umbraco.Core.Runtime }; } - protected virtual void ConfigureAssemblyResolve() - { - // When an assembly can't be resolved. In here we can do magic with the assembly name and try loading another. - // This is used for loading a signed assembly of AutoMapper (v. 3.1+) without having to recompile old code. - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => - { - // ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again - // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stack overflow - if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null")) - return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005")); - return null; - }; - } - protected virtual void ConfigureApplicationRootPath() { var path = GetApplicationRootPath(); @@ -242,7 +228,7 @@ namespace Umbraco.Core.Runtime } catch { - timer.Fail(); + timer?.Fail(); throw; } } diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 55255b418d..6fb8a04c0d 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -252,7 +252,7 @@ namespace Umbraco.Core FinalMigrationState = upgrader.Plan.FinalState; } - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", CurrentMigrationState, FinalMigrationState ?? ""); + logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? ""); return CurrentMigrationState == FinalMigrationState; } diff --git a/src/Umbraco.Core/Scoping/ScopeContextualBase.cs b/src/Umbraco.Core/Scoping/ScopeContextualBase.cs index 7461142234..25f176d471 100644 --- a/src/Umbraco.Core/Scoping/ScopeContextualBase.cs +++ b/src/Umbraco.Core/Scoping/ScopeContextualBase.cs @@ -2,39 +2,61 @@ namespace Umbraco.Core.Scoping { - // base class for an object that will be enlisted in scope context, if any. it - // must be used in a 'using' block, and if not scoped, released when disposed, - // else when scope context runs enlisted actions + /// + /// Provides a base class for scope contextual objects. + /// + /// + /// A scope contextual object is enlisted in the current scope context, + /// if any, and released when the context exists. It must be used in a 'using' + /// block, and will be released when disposed, if not part of a scope. + /// public abstract class ScopeContextualBase : IDisposable { - private bool _using, _scoped; + private bool _scoped; + /// + /// Gets a contextual object. + /// + /// The type of the object. + /// A scope provider. + /// A context key for the object. + /// A function producing the contextual object. + /// The contextual object. + /// + /// + /// public static T Get(IScopeProvider scopeProvider, string key, Func ctor) where T : ScopeContextualBase { + // no scope context = create a non-scoped object var scopeContext = scopeProvider.Context; if (scopeContext == null) return ctor(false); + // create & enlist the scoped object var w = scopeContext.Enlist("ScopeContextualBase_" + key, () => ctor(true), (completed, item) => { item.Release(completed); }); - if (w._using) throw new InvalidOperationException("panic: used."); - w._using = true; w._scoped = true; return w; } + /// + /// + /// If not scoped, then this releases the contextual object. + /// public void Dispose() { - _using = false; - if (_scoped == false) Release(true); } + /// + /// Releases the contextual object. + /// + /// A value indicating whether the scoped operation completed. public abstract void Release(bool completed); } } diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 3d87482d60..085a7b7a5b 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Linq; using System.Threading.Tasks; using System.Web.Security; -using AutoMapper; using Microsoft.AspNet.Identity; -using Microsoft.Owin; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; @@ -19,7 +17,7 @@ using Task = System.Threading.Tasks.Task; namespace Umbraco.Core.Security { - public class BackOfficeUserStore : DisposableObjectSlim, + public class BackOfficeUserStore : DisposableObjectSlim, IUserStore, IUserPasswordStore, IUserEmailStore, @@ -40,9 +38,10 @@ namespace Umbraco.Core.Security private readonly IEntityService _entityService; private readonly IExternalLoginService _externalLoginService; private readonly IGlobalSettings _globalSettings; + private readonly UmbracoMapper _mapper; private bool _disposed = false; - public BackOfficeUserStore(IUserService userService, IMemberTypeService memberTypeService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, MembershipProviderBase usersMembershipProvider) + public BackOfficeUserStore(IUserService userService, IMemberTypeService memberTypeService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, MembershipProviderBase usersMembershipProvider, UmbracoMapper mapper) { _userService = userService; _memberTypeService = memberTypeService; @@ -52,6 +51,7 @@ namespace Umbraco.Core.Security if (userService == null) throw new ArgumentNullException("userService"); if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider"); if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); + _mapper = mapper; _userService = userService; _externalLoginService = externalLoginService; @@ -185,7 +185,8 @@ namespace Umbraco.Core.Security { return null; } - return await Task.FromResult(AssignLoginsCallback(Mapper.Map(user))); + + return await Task.FromResult(AssignLoginsCallback(_mapper.Map(user))); } /// @@ -202,7 +203,7 @@ namespace Umbraco.Core.Security return null; } - var result = AssignLoginsCallback(Mapper.Map(user)); + var result = AssignLoginsCallback(_mapper.Map(user)); return await Task.FromResult(result); } @@ -314,7 +315,7 @@ namespace Umbraco.Core.Security var user = _userService.GetByEmail(email); var result = user == null ? null - : Mapper.Map(user); + : _mapper.Map(user); return Task.FromResult(AssignLoginsCallback(result)); } @@ -391,7 +392,7 @@ namespace Umbraco.Core.Security var user = _userService.GetUserById(l.UserId); if (user != null) { - output = Mapper.Map(user); + output = _mapper.Map(user); break; } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 5bdc0959da..a4def1d209 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Core.Services.Changes; @@ -505,7 +505,7 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable GetVersionIds(int id, int maxRows) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (ScopeProvider.CreateScope(autoComplete: true)) { return _documentRepository.GetVersionIds(id, maxRows); } @@ -533,7 +533,7 @@ namespace Umbraco.Core.Services.Implement //null check otherwise we get exceptions if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); - var rootId = Constants.System.Root.ToInvariantString(); + var rootId = Constants.System.RootString; var ids = content.Path.Split(',') .Where(x => x != rootId && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); if (ids.Any() == false) @@ -877,38 +877,15 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); - Property[] invalidProperties; - // if culture is specific, first publish the invariant values, then publish the culture itself. // if culture is '*', then publish them all (including variants) - // explicitly SaveAndPublish a specific culture also publishes invariant values - if (!culture.IsNullOrWhiteSpace() && culture != "*") - { - // publish the invariant values - var publishInvariant = content.PublishCulture(null); - if (!publishInvariant) - return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); - - //validate the property values - if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties)) - return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) - { - InvalidProperties = invalidProperties - }; - } + //this will create the correct culture impact even if culture is * or null + var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content); // publish the culture(s) - var publishCulture = content.PublishCulture(culture); - if (!publishCulture) - return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); - - //validate the property values - if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties)) - return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) - { - InvalidProperties = invalidProperties - }; + // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. + content.PublishCulture(impact); var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); scope.Complete(); @@ -941,16 +918,15 @@ namespace Umbraco.Core.Services.Implement : new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } + if (cultures.Any(x => x == null || x == "*")) + throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed"); - if (cultures.Select(content.PublishCulture).Any(isValid => !isValid)) - return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); + var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))); - //validate the property values - if (!_propertyValidationService.Value.IsPropertyDataValid(content, out var invalidProperties)) - return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) - { - InvalidProperties = invalidProperties - }; + // publish the culture(s) + // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. + foreach (var impact in impacts) + content.PublishCulture(impact); var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); scope.Complete(); @@ -1096,6 +1072,7 @@ namespace Umbraco.Core.Services.Implement if (publishing) { + //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo culturesUnpublishing = content.GetCulturesUnpublishing(); culturesPublishing = variesByCulture ? content.PublishCultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() @@ -1147,7 +1124,7 @@ namespace Umbraco.Core.Services.Implement // note: This unpublishes the entire document (not different variants) unpublishResult = StrategyCanUnpublish(scope, content, evtMsgs); if (unpublishResult.Success) - unpublishResult = StrategyUnpublish(scope, content, userId, evtMsgs); + unpublishResult = StrategyUnpublish(content, evtMsgs); else { // reset published state from temp values (publishing, unpublishing) to original value @@ -1330,7 +1307,8 @@ namespace Umbraco.Core.Services.Implement //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed Property[] invalidProperties = null; - var tryPublish = d.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties); + var impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture)); + var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); @@ -1426,9 +1404,17 @@ namespace Umbraco.Core.Services.Implement // variant content type - publish specified cultures // invariant content type - publish only the invariant culture - return content.ContentType.VariesByCulture() - ? culturesToPublish.All(culture => content.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(content, out _)) - : content.PublishCulture() && _propertyValidationService.Value.IsPropertyDataValid(content, out _); + if (content.ContentType.VariesByCulture()) + { + return culturesToPublish.All(culture => + { + var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content); + return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); + }); + } + + return content.PublishCulture(CultureImpact.Invariant) + && _propertyValidationService.Value.IsPropertyDataValid(content, out _, CultureImpact.Invariant); } // utility 'ShouldPublish' func used by SaveAndPublishBranch @@ -1922,7 +1908,7 @@ namespace Umbraco.Core.Services.Implement // if uow is not immediate, content.Path will be updated only when the UOW commits, // and because we want it now, we have to calculate it by ourselves //paths[content.Id] = content.Path; - paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : "-1") : parent.Path) + "," + content.Id; + paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString) : parent.Path) + "," + content.Id; const int pageSize = 500; var page = 0; @@ -2500,10 +2486,29 @@ namespace Umbraco.Core.Services.Implement var variesByCulture = content.ContentType.VariesByCulture(); - //First check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will + var impactsToPublish = culturesPublishing == null + ? new[] {CultureImpact.Invariant} //if it's null it's invariant + : culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray(); + + // publish the culture(s) + if (!impactsToPublish.All(content.PublishCulture)) + return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); + + //validate the property values + Property[] invalidProperties = null; + if (!impactsToPublish.All(x => _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x))) + return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) + { + InvalidProperties = invalidProperties + }; + + //Check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will // be changed to Unpublished and any culture currently published will not be visible. if (variesByCulture) { + if (culturesPublishing == null) + throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null."); + if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); @@ -2639,15 +2644,13 @@ namespace Umbraco.Core.Services.Implement /// /// Unpublishes a document /// - /// /// - /// /// /// /// /// It is assumed that all unpublishing checks have passed before calling this method like /// - private PublishResult StrategyUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs) + private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs) { var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content); @@ -2862,7 +2865,7 @@ namespace Umbraco.Core.Services.Implement { if (blueprint == null) throw new ArgumentNullException(nameof(blueprint)); - var contentType = _contentTypeRepository.Get(blueprint.ContentType.Id); + var contentType = GetContentType(blueprint.ContentType.Alias); var content = new Content(name, -1, contentType); content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); @@ -2875,7 +2878,14 @@ namespace Umbraco.Core.Services.Implement { foreach (var property in blueprint.Properties) { - content.SetValue(property.Alias, property.GetValue(culture), culture); + if (property.PropertyType.VariesByCulture()) + { + content.SetValue(property.Alias, property.GetValue(culture), culture); + } + else + { + content.SetValue(property.Alias, property.GetValue()); + } } content.Name = blueprint.Name; @@ -2892,7 +2902,7 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetBlueprintsForContentTypes(params int[] contentTypeId) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (ScopeProvider.CreateScope(autoComplete: true)) { var query = Query(); if (contentTypeId.Length > 0) diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs index 445daddff4..3552b2d8fc 100644 --- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs @@ -424,7 +424,6 @@ namespace Umbraco.Core.Services.Implement return; } - // find ContentTypes using this IDataTypeDefinition on a PropertyType, and delete // TODO: media and members?! // TODO: non-group properties?! diff --git a/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs index f620d31fe9..86913071fd 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizedTextServiceFileSources.cs @@ -184,8 +184,12 @@ namespace Umbraco.Core.Services.Implement //now load in supplementary var found = _supplementFileSources.Where(x => { - var fileName = Path.GetFileName(x.File.FullName); - return fileName.InvariantStartsWith(culture.Name) && fileName.InvariantEndsWith(".xml"); + var extension = Path.GetExtension(x.File.FullName); + var fileCultureName = Path.GetFileNameWithoutExtension(x.File.FullName).Replace("_", "-").Replace(".user", ""); + return extension.InvariantEquals(".xml") && ( + fileCultureName.InvariantEquals(culture.Name) + || fileCultureName.InvariantEquals(culture.TwoLetterISOLanguageName) + ); }); foreach (var supplementaryFile in found) @@ -203,7 +207,7 @@ namespace Umbraco.Core.Services.Implement continue; } - if (xChildDoc.Root == null) continue; + if (xChildDoc.Root == null || xChildDoc.Root.Name != "language") continue; foreach (var xArea in xChildDoc.Root.Elements("area") .Where(x => ((string)x.Attribute("alias")).IsNullOrWhiteSpace() == false)) { diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index a1aea45f11..fa72896239 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -995,7 +995,7 @@ namespace Umbraco.Core.Services.Implement // if uow is not immediate, content.Path will be updated only when the UOW commits, // and because we want it now, we have to calculate it by ourselves //paths[media.Id] = media.Path; - paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : "-1") : parent.Path) + "," + media.Id; + paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString) : parent.Path) + "," + media.Id; const int pageSize = 500; var page = 0; diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index 146173dd5c..b846095bd1 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -31,30 +31,34 @@ namespace Umbraco.Core.Services /// /// Validates the content item's properties pass validation rules /// - /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor - /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. - public bool IsPropertyDataValid(IContentBase content, out Property[] invalidProperties, string culture = "*") + public bool IsPropertyDataValid(IContent content, out Property[] invalidProperties, CultureImpact impact) { // select invalid properties invalidProperties = content.Properties.Where(x => { - // if culture is null, we validate invariant properties only - // if culture is '*' we validate both variant and invariant properties, automatically - // if culture is specific eg 'en-US' we both too, but explicitly + var propertyTypeVaries = x.PropertyType.VariesByCulture(); - var varies = x.PropertyType.VariesByCulture(); + // impacts invariant = validate invariant property, invariant culture + if (impact.ImpactsOnlyInvariantCulture) + return !(propertyTypeVaries || IsPropertyValid(x, null)); - if (culture == null) - return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture + // impacts all = validate property, all cultures (incl. invariant) + if (impact.ImpactsAllCultures) + return !IsPropertyValid(x); - if (culture == "*") - return !IsPropertyValid(x, culture); // validate property, all cultures + // impacts explicit culture = validate variant property, explicit culture + if (propertyTypeVaries) + return !IsPropertyValid(x, impact.Culture); - return varies - ? !IsPropertyValid(x, culture) // validate variant property, explicit culture - : !IsPropertyValid(x, null); // validate invariant property, explicit culture - }) - .ToArray(); + // and, for explicit culture, we may also have to validate invariant property, invariant culture + // if either + // - it is impacted (default culture), or + // - there is no published version of the content - maybe non-default culture, but no published version + + var alsoInvariant = impact.ImpactsAlsoInvariantProperties || !content.Published; + return alsoInvariant && !IsPropertyValid(x, null); + + }).ToArray(); return invalidProperties.Length == 0; } diff --git a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs index 91dd5a7597..54ca74d20f 100644 --- a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs +++ b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Strings /// Converts an Utf8 string into an Ascii string. /// /// The text to convert. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The converted text. public static string ToAsciiString(string text, char fail = '?') { @@ -39,7 +39,7 @@ namespace Umbraco.Core.Strings /// Converts an Utf8 string into an array of Ascii characters. /// /// The text to convert. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The converted text. public static char[] ToAsciiCharArray(string text, char fail = '?') { @@ -66,7 +66,7 @@ namespace Umbraco.Core.Strings /// /// The input array. /// The output array. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The number of characters in the output array. /// The caller must ensure that the output array is big enough. /// The output array is not big enough. @@ -112,7 +112,7 @@ namespace Umbraco.Core.Strings /// The input position. /// The output array. /// The output position. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// /// Adapted from various sources on the 'net including Lucene.Net.Analysis.ASCIIFoldingFilter. /// Input should contain Utf8 characters exclusively and NOT Unicode. diff --git a/src/Umbraco.Core/TypeLoaderExtensions.cs b/src/Umbraco.Core/TypeLoaderExtensions.cs index 96028daca6..974bd3e805 100644 --- a/src/Umbraco.Core/TypeLoaderExtensions.cs +++ b/src/Umbraco.Core/TypeLoaderExtensions.cs @@ -33,13 +33,5 @@ namespace Umbraco.Core { return mgr.GetTypes(); } - - /// - /// Gets all classes inheriting from BaseMapper and marked with the MapperForAttribute. - /// - public static IEnumerable GetAssignedMapperTypes(this TypeLoader mgr) - { - return mgr.GetTypesWithAttribute(); - } } } diff --git a/src/Umbraco.Core/UdiEntityType.cs b/src/Umbraco.Core/UdiEntityType.cs index 86197ce6a8..75a137bd2e 100644 --- a/src/Umbraco.Core/UdiEntityType.cs +++ b/src/Umbraco.Core/UdiEntityType.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core internal static Dictionary GetTypes() { - return new Dictionary + return new Dictionary { { Unknown, UdiType.Unknown }, diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c0149ccadb..68091fb3a9 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -55,7 +55,11 @@ - + + 1.0.5 + runtime; build; native; contentfiles; analyzers + all + @@ -161,6 +165,7 @@ + @@ -168,7 +173,6 @@ - @@ -208,12 +212,24 @@ + + + + + + + + + + + + @@ -437,7 +453,7 @@ - + @@ -530,7 +546,7 @@ - + @@ -851,8 +867,7 @@ - - + @@ -947,7 +962,6 @@ - @@ -983,7 +997,6 @@ - @@ -1311,7 +1324,7 @@ - + diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 083ca90cc4..1198b26e0f 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -23,21 +23,21 @@ namespace Umbraco.Core /// /// /// There are some special routes we need to check to properly determine this: - /// + /// /// If any route has an extension in the path like .aspx = back office - /// + /// /// These are def back office: /// /Umbraco/BackOffice = back office /// /Umbraco/Preview = back office /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. - /// + /// /// These are def front-end: /// /Umbraco/Surface = front-end /// /Umbraco/Api = front-end /// But if we've got this far we'll just have to assume it's front-end anyways. - /// + /// /// internal static bool IsBackOfficeRequest(this Uri url, string applicationPath, IGlobalSettings globalSettings) { @@ -152,9 +152,9 @@ namespace Umbraco.Core var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"}; return toInclude.Any(ext.InvariantEquals) == false; } - catch (ArgumentException ex) + catch (ArgumentException) { - Current.Logger.Error(typeof(UriExtensions), ex, "Failed to determine if request was client side"); + Current.Logger.Debug(typeof(UriExtensions), "Failed to determine if request was client side (invalid chars in path \"{Path}\"?)", url.LocalPath); return false; } } diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index a852f97002..3fd8dcaea5 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Xml /// /// The XmlHelper class contains general helper methods for working with xml in umbraco. /// - internal class XmlHelper + public class XmlHelper { /// /// Creates or sets an attribute on the XmlNode if an Attributes collection is available @@ -341,7 +341,7 @@ namespace Umbraco.Core.Xml /// /// The XmlNode. /// the value as a string - public static string GetNodeValue(XmlNode n) + internal static string GetNodeValue(XmlNode n) { var value = string.Empty; if (n == null || n.FirstChild == null) @@ -373,7 +373,7 @@ namespace Umbraco.Core.Xml /// Name of the root. /// Name of the element. /// Returns an System.Xml.XmlDocument representation of the delimited string data. - public static XmlDocument Split(string data, string[] separator, string rootName, string elementName) + internal static XmlDocument Split(string data, string[] separator, string rootName, string elementName) { return Split(new XmlDocument(), data, separator, rootName, elementName); } @@ -387,7 +387,7 @@ namespace Umbraco.Core.Xml /// Name of the root node. /// Name of the element node. /// Returns an System.Xml.XmlDocument representation of the delimited string data. - public static XmlDocument Split(XmlDocument xml, string data, string[] separator, string rootName, string elementName) + internal static XmlDocument Split(XmlDocument xml, string data, string[] separator, string rootName, string elementName) { // load new XML document. xml.LoadXml(string.Concat("<", rootName, "/>")); @@ -416,7 +416,7 @@ namespace Umbraco.Core.Xml /// /// /// - public static Dictionary GetAttributesFromElement(string tag) + internal static Dictionary GetAttributesFromElement(string tag) { var m = Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 19171ac6b1..43a3ccc196 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -28,6 +28,8 @@ namespace Umbraco.Examine /// internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled); + //TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression + /// /// Returns all index fields that are culture specific (suffixed) /// diff --git a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs index 3532eba6b2..bd36013c9f 100644 --- a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Linq.Expressions; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; using Moq; using Umbraco.Core.Models; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; @@ -13,9 +15,20 @@ namespace Umbraco.Tests.Benchmarks [MemoryDiagnoser] public class ModelToSqlExpressionHelperBenchmarks { + protected Lazy MockSqlContext() + { + var sqlContext = Mock.Of(); + var syntax = new SqlCeSyntaxProvider(); + Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(syntax); + return new Lazy(() => sqlContext); + } + + protected ConcurrentDictionary> CreateMaps() + => new ConcurrentDictionary>(); + public ModelToSqlExpressionHelperBenchmarks() { - var contentMapper = new ContentMapper(); + var contentMapper = new ContentMapper(MockSqlContext(), CreateMaps()); _cachedExpression = new CachedExpression(); var mapperCollection = new Mock(); mapperCollection.Setup(x => x[It.IsAny()]).Returns(contentMapper); diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 49de625450..91047ba6a2 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -54,62 +54,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index 6e8835b9a5..c092306473 100644 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -158,7 +158,7 @@ namespace Umbraco.Tests.Cache new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), TestObjects.GetGlobalSettings(), - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); // just assert it does not throw diff --git a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs index 013dbadbb8..b435af9e77 100644 --- a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs +++ b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs @@ -5,6 +5,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Scoping; using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Tests.Cache { @@ -388,8 +389,7 @@ namespace Umbraco.Tests.Cache // collect liveGen GC.Collect(); - SnapDictionary.GenerationObject genObj; - Assert.IsTrue(d.Test.GenerationObjects.TryPeek(out genObj)); + Assert.IsTrue(d.Test.GenObjs.TryPeek(out var genObj)); genObj = null; // in Release mode, it works, but in Debug mode, the weak reference is still alive @@ -399,14 +399,14 @@ namespace Umbraco.Tests.Cache GC.Collect(); #endif - Assert.IsTrue(d.Test.GenerationObjects.TryPeek(out genObj)); - Assert.IsFalse(genObj.WeakReference.IsAlive); // snapshot is gone, along with its reference + Assert.IsTrue(d.Test.GenObjs.TryPeek(out genObj)); + Assert.IsFalse(genObj.WeakGenRef.IsAlive); // snapshot is gone, along with its reference await d.CollectAsync(); Assert.AreEqual(0, d.Test.GetValues(1).Length); // null value is gone Assert.AreEqual(0, d.Count); // item is gone - Assert.AreEqual(0, d.Test.GenerationObjects.Count); + Assert.AreEqual(0, d.Test.GenObjs.Count); Assert.AreEqual(0, d.SnapCount); // snapshot is gone Assert.AreEqual(0, d.GenCount); // and generation has been dequeued } @@ -632,7 +632,7 @@ namespace Umbraco.Tests.Cache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - using (d.GetWriter(GetScopeProvider())) + using (d.GetScopedWriteLock(GetScopeProvider())) { var s1 = d.CreateSnapshot(); @@ -685,7 +685,7 @@ namespace Umbraco.Tests.Cache Assert.IsFalse(d.Test.NextGen); Assert.AreEqual("uno", s2.Get(1)); - using (d.GetWriter(GetScopeProvider())) + using (d.GetScopedWriteLock(GetScopeProvider())) { // gen 3 Assert.AreEqual(2, d.Test.GetValues(1).Length); @@ -712,16 +712,102 @@ namespace Umbraco.Tests.Cache } [Test] - public void NestedWriteLocking() + public void NestedWriteLocking1() + { + var d = new SnapDictionary(); + var t = d.Test; + t.CollectAuto = false; + + Assert.AreEqual(0, d.CreateSnapshot().Gen); + + // no scope context: writers nest, last one to be disposed commits + + var scopeProvider = GetScopeProvider(); + + using (var w1 = d.GetScopedWriteLock(scopeProvider)) + { + Assert.AreEqual(1, t.LiveGen); + Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.NextGen); + + using (var w2 = d.GetScopedWriteLock(scopeProvider)) + { + Assert.AreEqual(1, t.LiveGen); + Assert.AreEqual(2, t.WLocked); + Assert.IsTrue(t.NextGen); + + Assert.AreNotSame(w1, w2); // get a new writer each time + + d.Set(1, "one"); + + Assert.AreEqual(0, d.CreateSnapshot().Gen); + } + + Assert.AreEqual(1, t.LiveGen); + Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.NextGen); + + Assert.AreEqual(0, d.CreateSnapshot().Gen); + } + + Assert.AreEqual(1, t.LiveGen); + Assert.AreEqual(0, t.WLocked); + Assert.IsTrue(t.NextGen); + + Assert.AreEqual(1, d.CreateSnapshot().Gen); + } + + [Test] + public void NestedWriteLocking2() { var d = new SnapDictionary(); d.Test.CollectAuto = false; - var scopeProvider = GetScopeProvider(); - using (d.GetWriter(scopeProvider)) + Assert.AreEqual(0, d.CreateSnapshot().Gen); + + // scope context: writers enlist + + var scopeContext = new ScopeContext(); + var scopeProvider = GetScopeProvider(scopeContext); + + using (var w1 = d.GetScopedWriteLock(scopeProvider)) { - using (d.GetWriter(scopeProvider)) + using (var w2 = d.GetScopedWriteLock(scopeProvider)) { + Assert.AreSame(w1, w2); + + d.Set(1, "one"); + } + } + } + + [Test] + public void NestedWriteLocking3() + { + var d = new SnapDictionary(); + var t = d.Test; + t.CollectAuto = false; + + Assert.AreEqual(0, d.CreateSnapshot().Gen); + + var scopeContext = new ScopeContext(); + var scopeProvider1 = GetScopeProvider(); + var scopeProvider2 = GetScopeProvider(scopeContext); + + using (var w1 = d.GetScopedWriteLock(scopeProvider1)) + { + Assert.AreEqual(1, t.LiveGen); + Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.NextGen); + + using (var w2 = d.GetScopedWriteLock(scopeProvider2)) + { + Assert.AreEqual(1, t.LiveGen); + Assert.AreEqual(2, t.WLocked); + Assert.IsTrue(t.NextGen); + + Assert.AreNotSame(w1, w2); + d.Set(1, "one"); } } @@ -764,7 +850,7 @@ namespace Umbraco.Tests.Cache var scopeProvider = GetScopeProvider(); - using (d.GetWriter(scopeProvider)) + using (d.GetScopedWriteLock(scopeProvider)) { // gen 3 Assert.AreEqual(2, d.Test.GetValues(1).Length); @@ -809,7 +895,7 @@ namespace Umbraco.Tests.Cache var scopeProvider = GetScopeProvider(); - using (d.GetWriter(scopeProvider)) + using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release @@ -846,9 +932,10 @@ namespace Umbraco.Tests.Cache Assert.AreEqual(2, s2.Gen); Assert.AreEqual("uno", s2.Get(1)); - var scopeProvider = GetScopeProvider(true); + var scopeContext = new ScopeContext(); + var scopeProvider = GetScopeProvider(scopeContext); - using (d.GetWriter(scopeProvider)) + using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release @@ -867,7 +954,7 @@ namespace Umbraco.Tests.Cache Assert.AreEqual(2, s4.Gen); Assert.AreEqual("uno", s4.Get(1)); - ((ScopeContext) scopeProvider.Context).ScopeExit(true); + scopeContext.ScopeExit(true); var s5 = d.CreateSnapshot(); Assert.AreEqual(3, s5.Gen); @@ -878,7 +965,8 @@ namespace Umbraco.Tests.Cache public void ScopeLocking2() { var d = new SnapDictionary(); - d.Test.CollectAuto = false; + var t = d.Test; + t.CollectAuto = false; // gen 1 d.Set(1, "one"); @@ -891,12 +979,13 @@ namespace Umbraco.Tests.Cache Assert.AreEqual(2, s2.Gen); Assert.AreEqual("uno", s2.Get(1)); - var scopeProviderMock = new Mock(); - var scopeContext = new ScopeContext(); - scopeProviderMock.Setup(x => x.Context).Returns(scopeContext); - var scopeProvider = scopeProviderMock.Object; + Assert.AreEqual(2, t.LiveGen); + Assert.IsFalse(t.NextGen); - using (d.GetWriter(scopeProvider)) + var scopeContext = new ScopeContext(); + var scopeProvider = GetScopeProvider(scopeContext); + + using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release @@ -905,18 +994,35 @@ namespace Umbraco.Tests.Cache Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); + // we made some changes, so a next gen is required + Assert.AreEqual(3, t.LiveGen); + Assert.IsTrue(t.NextGen); + Assert.AreEqual(1, t.WLocked); + // but live snapshot contains changes - var ls = d.Test.LiveSnapshot; + var ls = t.LiveSnapshot; Assert.AreEqual("ein", ls.Get(1)); Assert.AreEqual(3, ls.Gen); } + // nothing is committed until scope exits + Assert.AreEqual(3, t.LiveGen); + Assert.IsTrue(t.NextGen); + Assert.AreEqual(1, t.WLocked); + + // no changes until exit var s4 = d.CreateSnapshot(); Assert.AreEqual(2, s4.Gen); Assert.AreEqual("uno", s4.Get(1)); scopeContext.ScopeExit(false); + // now things have changed + Assert.AreEqual(2, t.LiveGen); + Assert.IsFalse(t.NextGen); + Assert.AreEqual(0, t.WLocked); + + // no changes since not completed var s5 = d.CreateSnapshot(); Assert.AreEqual(2, s5.Gen); Assert.AreEqual("uno", s5.Get(1)); @@ -955,12 +1061,92 @@ namespace Umbraco.Tests.Cache Assert.AreEqual("four", all[3]); } - private IScopeProvider GetScopeProvider(bool withContext = false) + [Test] + public void DontPanic() { - var scopeProviderMock = new Mock(); - var scopeContext = withContext ? new ScopeContext() : null; - scopeProviderMock.Setup(x => x.Context).Returns(scopeContext); - var scopeProvider = scopeProviderMock.Object; + var d = new SnapDictionary(); + d.Test.CollectAuto = false; + + Assert.IsNull(d.Test.GenObj); + + // gen 1 + d.Set(1, "one"); + Assert.IsTrue(d.Test.NextGen); + Assert.AreEqual(1, d.Test.LiveGen); + Assert.IsNull(d.Test.GenObj); + + var s1 = d.CreateSnapshot(); + Assert.IsFalse(d.Test.NextGen); + Assert.AreEqual(1, d.Test.LiveGen); + Assert.IsNotNull(d.Test.GenObj); + Assert.AreEqual(1, d.Test.GenObj.Gen); + + Assert.AreEqual(1, s1.Gen); + Assert.AreEqual("one", s1.Get(1)); + + d.Set(1, "uno"); + Assert.IsTrue(d.Test.NextGen); + Assert.AreEqual(2, d.Test.LiveGen); + Assert.IsNotNull(d.Test.GenObj); + Assert.AreEqual(1, d.Test.GenObj.Gen); + + var scopeContext = new ScopeContext(); + var scopeProvider = GetScopeProvider(scopeContext); + + // scopeProvider.Context == scopeContext -> writer is scoped + // writer is scope contextual and scoped + // when disposed, nothing happens + // when the context exists, the writer is released + using (d.GetScopedWriteLock(scopeProvider)) + { + d.Set(1, "ein"); + Assert.IsTrue(d.Test.NextGen); + Assert.AreEqual(3, d.Test.LiveGen); + Assert.IsNotNull(d.Test.GenObj); + Assert.AreEqual(2, d.Test.GenObj.Gen); + } + + // writer has not released + Assert.AreEqual(1, d.Test.WLocked); + Assert.IsNotNull(d.Test.GenObj); + Assert.AreEqual(2, d.Test.GenObj.Gen); + + // nothing changed + Assert.IsTrue(d.Test.NextGen); + Assert.AreEqual(3, d.Test.LiveGen); + + // panic! + var s2 = d.CreateSnapshot(); + + Assert.AreEqual(1, d.Test.WLocked); + Assert.IsNotNull(d.Test.GenObj); + Assert.AreEqual(2, d.Test.GenObj.Gen); + Assert.AreEqual(3, d.Test.LiveGen); + Assert.IsTrue(d.Test.NextGen); + + // release writer + scopeContext.ScopeExit(true); + + Assert.AreEqual(0, d.Test.WLocked); + Assert.IsNotNull(d.Test.GenObj); + Assert.AreEqual(2, d.Test.GenObj.Gen); + Assert.AreEqual(3, d.Test.LiveGen); + Assert.IsTrue(d.Test.NextGen); + + var s3 = d.CreateSnapshot(); + + Assert.AreEqual(0, d.Test.WLocked); + Assert.IsNotNull(d.Test.GenObj); + Assert.AreEqual(3, d.Test.GenObj.Gen); + Assert.AreEqual(3, d.Test.LiveGen); + Assert.IsFalse(d.Test.NextGen); + } + + private IScopeProvider GetScopeProvider(ScopeContext scopeContext = null) + { + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.Context).Returns(scopeContext); return scopeProvider; } } diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs new file mode 100644 index 0000000000..f4478e2add --- /dev/null +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -0,0 +1,53 @@ +using System; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Tests.Composing +{ + [TestFixture] + public class CompositionTests + { + [Test] + public void FactoryIsResolvable() + { + Func factoryFactory = null; + + var mockedRegister = Mock.Of(); + var mockedFactory = Mock.Of(); + + // the mocked register creates the mocked factory + Mock.Get(mockedRegister) + .Setup(x => x.CreateFactory()) + .Returns(mockedFactory); + + // the mocked register can register a factory factory + Mock.Get(mockedRegister) + .Setup(x => x.Register(It.IsAny>(), Lifetime.Singleton)) + .Callback, Lifetime>((ff, lt) => factoryFactory = ff); + + // the mocked factory can invoke the factory factory + Mock.Get(mockedFactory) + .Setup(x => x.GetInstance(typeof(IFactory))) + .Returns(() => factoryFactory?.Invoke(mockedFactory)); + + var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); + var typeLoader = new TypeLoader(Mock.Of(), "", logger); + var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of()); + + // create the factory, ensure it is the mocked factory + var factory = composition.CreateFactory(); + Assert.AreSame(mockedFactory, factory); + + // ensure we can get an IFactory instance, + // meaning that it has been properly registered + + var resolved = factory.GetInstance(); + Assert.IsNotNull(resolved); + Assert.AreSame(factory, resolved); + } + } +} diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index b3237dd4ba..7b7574ce47 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -260,13 +260,6 @@ AnotherContentFinder Assert.AreEqual(1, _typeLoader.TypeLists.Count(x => x.BaseType == typeof(IFindMe) && x.AttributeType == null)); } - [Test] - public void Resolves_Assigned_Mappers() - { - var foundTypes1 = _typeLoader.GetAssignedMapperTypes(); - Assert.AreEqual(30, foundTypes1.Count()); - } - [Test] public void Resolves_Types() { diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs index 75e2c66a61..35981f5368 100644 --- a/src/Umbraco.Tests/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using System; using System.IO; using System.Linq; @@ -47,7 +48,8 @@ namespace Umbraco.Tests.Logging File.Copy(exampleLogfilePath, _newLogfilePath, true); File.Copy(exampleSearchfilePath, _newSearchfilePath, true); - _logViewer = new JsonLogViewer(logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath); + var logger = Mock.Of(); + _logViewer = new JsonLogViewer(logger, logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath); } [OneTimeTearDown] diff --git a/src/Umbraco.Tests/Mapping/MappingTests.cs b/src/Umbraco.Tests/Mapping/MappingTests.cs new file mode 100644 index 0000000000..3435050cc5 --- /dev/null +++ b/src/Umbraco.Tests/Mapping/MappingTests.cs @@ -0,0 +1,148 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Mapping; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Tests.Mapping +{ + [TestFixture] + public class MappingTests + { + [Test] + public void SimpleMap() + { + var definitions = new MapDefinitionCollection(new IMapDefinition[] + { + new MapperDefinition1(), + }); + var mapper = new UmbracoMapper(definitions); + + var thing1 = new Thing1 { Value = "value" }; + var thing2 = mapper.Map(thing1); + + Assert.IsNotNull(thing2); + Assert.AreEqual("value", thing2.Value); + + thing2 = mapper.Map(thing1); + + Assert.IsNotNull(thing2); + Assert.AreEqual("value", thing2.Value); + + thing2 = new Thing2(); + mapper.Map(thing1, thing2); + Assert.AreEqual("value", thing2.Value); + } + + [Test] + public void EnumerableMap() + { + var definitions = new MapDefinitionCollection(new IMapDefinition[] + { + new MapperDefinition1(), + }); + var mapper = new UmbracoMapper(definitions); + + var thing1A = new Thing1 { Value = "valueA" }; + var thing1B = new Thing1 { Value = "valueB" }; + var thing1 = new[] { thing1A, thing1B }; + var thing2 = mapper.Map, IEnumerable>(thing1).ToList(); + + Assert.IsNotNull(thing2); + Assert.AreEqual(2, thing2.Count); + Assert.AreEqual("valueA", thing2[0].Value); + Assert.AreEqual("valueB", thing2[1].Value); + + thing2 = mapper.Map>(thing1).ToList(); + + Assert.IsNotNull(thing2); + Assert.AreEqual(2, thing2.Count); + Assert.AreEqual("valueA", thing2[0].Value); + Assert.AreEqual("valueB", thing2[1].Value); + + thing2 = mapper.MapEnumerable(thing1).ToList(); + + Assert.IsNotNull(thing2); + Assert.AreEqual(2, thing2.Count); + Assert.AreEqual("valueA", thing2[0].Value); + Assert.AreEqual("valueB", thing2[1].Value); + } + + [Test] + public void InheritedMap() + { + var definitions = new MapDefinitionCollection(new IMapDefinition[] + { + new MapperDefinition1(), + }); + var mapper = new UmbracoMapper(definitions); + + var thing3 = new Thing3 { Value = "value" }; + var thing2 = mapper.Map(thing3); + + Assert.IsNotNull(thing2); + Assert.AreEqual("value", thing2.Value); + + thing2 = mapper.Map(thing3); + + Assert.IsNotNull(thing2); + Assert.AreEqual("value", thing2.Value); + + thing2 = new Thing2(); + mapper.Map(thing3, thing2); + Assert.AreEqual("value", thing2.Value); + } + + [Test] + public void CollectionsMap() + { + var definitions = new MapDefinitionCollection(new IMapDefinition[] + { + new MapperDefinition2(), + }); + var mapper = new UmbracoMapper(definitions); + + // can map a PropertyCollection + var source = new PropertyCollection(); + var target = mapper.Map>(source); + } + + private class Thing1 + { + public string Value { get; set; } + } + + private class Thing3 : Thing1 + { } + + private class Thing2 + { + public string Value { get; set; } + } + + private class MapperDefinition1 : IMapDefinition + { + public void DefineMaps(UmbracoMapper mapper) + { + mapper.Define((source, context) => new Thing2(), Map); + } + + private void Map(Thing1 source, Thing2 target, MapperContext context) + { + target.Value = source.Value; + } + } + + private class MapperDefinition2 : IMapDefinition + { + public void DefineMaps(UmbracoMapper mapper) + { + mapper.Define((source, context) => new ContentPropertyDto(), Map); + } + + private static void Map(Property source, ContentPropertyDto target, MapperContext context) + { } + } + } +} diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 05f726893e..3116087669 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -104,7 +104,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - content.PublishCulture(langFr); //we've set the name, now we're publishing it + content.PublishCulture(CultureImpact.Explicit(langFr, false)); //we've set the name, now we're publishing it Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //now it will be changed since the collection has changed var frCultureName = content.PublishCultureInfos[langFr]; Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - content.PublishCulture(langFr); //we've set the name, now we're publishing it + content.PublishCulture(CultureImpact.Explicit(langFr, false)); //we've set the name, now we're publishing it Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //it's true now since we've updated a name } @@ -303,7 +303,7 @@ namespace Umbraco.Tests.Models content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); - content.PublishCulture("en-US"); + content.PublishCulture(CultureImpact.All); // should not try to clone something that's not Published or Unpublished // (and in fact it will not work) @@ -414,7 +414,7 @@ namespace Umbraco.Tests.Models content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); - content.PublishCulture("en-US"); + content.PublishCulture(CultureImpact.All); var i = 200; foreach (var property in content.Properties) diff --git a/src/Umbraco.Tests/Models/CultureImpactTests.cs b/src/Umbraco.Tests/Models/CultureImpactTests.cs new file mode 100644 index 0000000000..6cad634a14 --- /dev/null +++ b/src/Umbraco.Tests/Models/CultureImpactTests.cs @@ -0,0 +1,163 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class CultureImpactTests + { + [Test] + public void Get_Culture_For_Invariant_Errors() + { + var result = CultureImpact.GetCultureForInvariantErrors( + Mock.Of(x => x.Published == true), + new[] { "en-US", "fr-FR" }, + "en-US"); + Assert.AreEqual("en-US", result); //default culture is being saved so use it + + result = CultureImpact.GetCultureForInvariantErrors( + Mock.Of(x => x.Published == false), + new[] { "fr-FR" }, + "en-US"); + Assert.AreEqual("fr-FR", result); //default culture not being saved with not published version, use the first culture being saved + + result = CultureImpact.GetCultureForInvariantErrors( + Mock.Of(x => x.Published == true), + new[] { "fr-FR" }, + "en-US"); + Assert.AreEqual(null, result); //default culture not being saved with published version, use null + + } + + [Test] + public void All_Cultures() + { + var impact = CultureImpact.All; + + Assert.AreEqual(impact.Culture, "*"); + + Assert.IsTrue(impact.ImpactsInvariantProperties); + Assert.IsFalse(impact.ImpactsAlsoInvariantProperties); + Assert.IsFalse(impact.ImpactsOnlyInvariantCulture); + Assert.IsFalse(impact.ImpactsExplicitCulture); + Assert.IsTrue(impact.ImpactsAllCultures); + Assert.IsFalse(impact.ImpactsOnlyDefaultCulture); + } + + [Test] + public void Invariant_Culture() + { + var impact = CultureImpact.Invariant; + + Assert.AreEqual(impact.Culture, null); + + Assert.IsTrue(impact.ImpactsInvariantProperties); + Assert.IsFalse(impact.ImpactsAlsoInvariantProperties); + Assert.IsTrue(impact.ImpactsOnlyInvariantCulture); + Assert.IsFalse(impact.ImpactsExplicitCulture); + Assert.IsFalse(impact.ImpactsAllCultures); + Assert.IsFalse(impact.ImpactsOnlyDefaultCulture); + } + + [Test] + public void Explicit_Default_Culture() + { + var impact = CultureImpact.Explicit("en-US", true); + + Assert.AreEqual(impact.Culture, "en-US"); + + Assert.IsTrue(impact.ImpactsInvariantProperties); + Assert.IsTrue(impact.ImpactsAlsoInvariantProperties); + Assert.IsFalse(impact.ImpactsOnlyInvariantCulture); + Assert.IsTrue(impact.ImpactsExplicitCulture); + Assert.IsFalse(impact.ImpactsAllCultures); + Assert.IsTrue(impact.ImpactsOnlyDefaultCulture); + } + + [Test] + public void Explicit_NonDefault_Culture() + { + var impact = CultureImpact.Explicit("en-US", false); + + Assert.AreEqual(impact.Culture, "en-US"); + + Assert.IsFalse(impact.ImpactsInvariantProperties); + Assert.IsFalse(impact.ImpactsAlsoInvariantProperties); + Assert.IsFalse(impact.ImpactsOnlyInvariantCulture); + Assert.IsTrue(impact.ImpactsExplicitCulture); + Assert.IsFalse(impact.ImpactsAllCultures); + Assert.IsFalse(impact.ImpactsOnlyDefaultCulture); + } + + [Test] + public void TryCreate_Explicit_Default_Culture() + { + var success = CultureImpact.TryCreate("en-US", true, ContentVariation.Culture, false, out var impact); + Assert.IsTrue(success); + + Assert.AreEqual(impact.Culture, "en-US"); + + Assert.IsTrue(impact.ImpactsInvariantProperties); + Assert.IsTrue(impact.ImpactsAlsoInvariantProperties); + Assert.IsFalse(impact.ImpactsOnlyInvariantCulture); + Assert.IsTrue(impact.ImpactsExplicitCulture); + Assert.IsFalse(impact.ImpactsAllCultures); + Assert.IsTrue(impact.ImpactsOnlyDefaultCulture); + } + + [Test] + public void TryCreate_Explicit_NonDefault_Culture() + { + var success = CultureImpact.TryCreate("en-US", false, ContentVariation.Culture, false, out var impact); + Assert.IsTrue(success); + + Assert.AreEqual(impact.Culture, "en-US"); + + Assert.IsFalse(impact.ImpactsInvariantProperties); + Assert.IsFalse(impact.ImpactsAlsoInvariantProperties); + Assert.IsFalse(impact.ImpactsOnlyInvariantCulture); + Assert.IsTrue(impact.ImpactsExplicitCulture); + Assert.IsFalse(impact.ImpactsAllCultures); + Assert.IsFalse(impact.ImpactsOnlyDefaultCulture); + } + + [Test] + public void TryCreate_AllCultures_For_Invariant() + { + var success = CultureImpact.TryCreate("*", false, ContentVariation.Nothing, false, out var impact); + Assert.IsTrue(success); + + Assert.AreEqual(impact.Culture, null); + + Assert.AreSame(CultureImpact.Invariant, impact); + } + + [Test] + public void TryCreate_AllCultures_For_Variant() + { + var success = CultureImpact.TryCreate("*", false, ContentVariation.Culture, false, out var impact); + Assert.IsTrue(success); + + Assert.AreEqual(impact.Culture, "*"); + + Assert.AreSame(CultureImpact.All, impact); + } + + [Test] + public void TryCreate_Invariant_For_Variant() + { + var success = CultureImpact.TryCreate(null, false, ContentVariation.Culture, false, out var impact); + Assert.IsFalse(success); + } + + [Test] + public void TryCreate_Invariant_For_Invariant() + { + var success = CultureImpact.TryCreate(null, false, ContentVariation.Nothing, false, out var impact); + Assert.IsTrue(success); + + Assert.AreSame(CultureImpact.Invariant, impact); + } + } +} diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs deleted file mode 100644 index 18bceaae49..0000000000 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using AutoMapper; -using NUnit.Framework; - -namespace Umbraco.Tests.Models.Mapping -{ - [TestFixture] - public class AutoMapper6Tests - { - [Test] - public void Test1() - { - ThingProfile.CtorCount = 0; - Assert.AreEqual(0, ThingProfile.CtorCount); - - var config = new MapperConfiguration(cfg => - { - cfg.AddProfile(); - }); - - Assert.AreEqual(1, ThingProfile.CtorCount); - Assert.AreEqual(0, MemberValueResolver.CtorCount); - - var mapper = config.CreateMapper(); - - Assert.AreEqual(1, ThingProfile.CtorCount); - Assert.AreEqual(0, MemberValueResolver.CtorCount); - - var thingA = new ThingA { ValueInt = 42, ValueString = "foo" }; - var thingB = mapper.Map(thingA); - Assert.AreEqual(42, thingB.ValueInt); - Assert.AreEqual("!!foo!!", thingB.ValueString); - - mapper.Map(thingA); - mapper.Map(thingA); - mapper.Map(thingA); - Assert.AreEqual(1, ThingProfile.CtorCount); // one single profile - Assert.AreEqual(4, MemberValueResolver.CtorCount); // many resolvers - } - - [Test] - public void Test2() - { - var config = new MapperConfiguration(cfg => - { - cfg.AddProfile(); - }); - - var mapper = config.CreateMapper(); - - Assert.AreEqual(0, ValueResolver.CtorCount); - - var thingA = new ThingA { ValueInt = 42, ValueString = "foo" }; - var thingB = mapper.Map(thingA); - Assert.AreEqual(42, thingB.ValueInt); - Assert.AreEqual("!!foo!!", thingB.ValueString); - - mapper.Map(thingA); - mapper.Map(thingA); - mapper.Map(thingA); - Assert.AreEqual(4, ValueResolver.CtorCount); // many resolvers - } - - [Test] - public void Test3() - { - var config = new MapperConfiguration(cfg => - { - cfg.AddProfile(); - }); - - var mapper = config.CreateMapper(); - - var thingA = new ThingA { ValueInt = 42, ValueString = "foo" }; - var thingB = mapper.Map(thingA); - Assert.AreEqual(42, thingB.ValueInt); - Assert.AreEqual("!!foo!!", thingB.ValueString); - - mapper.Map(thingA); - mapper.Map(thingA); - mapper.Map(thingA); - } - - // Resolve destination member using a custom value resolver - // void ResolveUsing() - // where TValueResolver : IValueResolver; - - // Resolve destination member using a custom value resolver from a source member - // void ResolveUsing(Expression> sourceMember) - // where TValueResolver : IMemberValueResolver; - // void ResolveUsing(string sourceMemberName) - // where TValueResolver : IMemberValueResolver; - - // Resolve destination member using a custom value resolver instance - // void ResolveUsing(IValueResolver valueResolver); - // void ResolveUsing(IMemberValueResolver valueResolver, Expression> sourceMember); - - // Resolve destination member using a custom value resolver callback - // void ResolveUsing(Func resolver); - // void ResolveUsing(Func resolver); - // void ResolveUsing(Func resolver); - // void ResolveUsing(Func resolver); - - // read https://stackoverflow.com/questions/14875075/automapper-what-is-the-difference-between-mapfrom-and-resolveusing - // about the diff between MapFrom and ResolveUsing... keeping ResolveUsing in our code - - public class ThingProfile : Profile - { - public static int CtorCount { get; set; } - - public ThingProfile(int ver) - { - CtorCount++; - - var map = CreateMap() - .ForMember(dest => dest.ValueInt, opt => opt.MapFrom(src => src.ValueInt)); - - switch (ver) - { - case 0: - break; - case 1: - map - .ForMember(dest => dest.ValueString, opt => opt.MapFrom(src => src.ValueString)); - break; - case 2: - map - .ForMember(dest => dest.ValueString, opt => opt.MapFrom()); - break; - case 3: - // in most cases that should be perfectly enough? - map - .ForMember(dest => dest.ValueString, opt => opt.MapFrom(source => "!!" + source.ValueString + "!!")); - break; - default: - throw new ArgumentOutOfRangeException(nameof(ver)); - } - } - } - - public class ThingProfile1 : ThingProfile - { - public ThingProfile1() : base(1) { } - } - - public class ThingProfile2 : ThingProfile - { - public ThingProfile2() : base(2) { } - } - - public class ThingProfile3 : ThingProfile - { - public ThingProfile3() : base(3) { } - } - - public class ValueResolver : IValueResolver - { - public static int CtorCount { get; set; } - - public ValueResolver() - { - CtorCount++; - } - - public string Resolve(ThingA source, ThingB destination, string destMember, ResolutionContext context) - { - return "!!" + source.ValueString + "!!"; - } - } - - public class MemberValueResolver : IMemberValueResolver - { - public static int CtorCount { get; set; } - - public MemberValueResolver() - { - CtorCount++; - } - - public string Resolve(ThingA source, ThingB destination, string sourceMember, string destMember, ResolutionContext context) - { - return "!!" + sourceMember + "!!"; - } - } - - public class ThingA - { - public int ValueInt { get; set; } - public string ValueString { get; set; } - } - - public class ThingB - { - public int ValueInt { get; set; } - public string ValueString { get; set; } - } - } -} diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs deleted file mode 100644 index 57d38e342e..0000000000 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using NUnit.Framework; -using Umbraco.Core.Cache; -using Umbraco.Core.Manifest; -using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Models.Mapping -{ - [TestFixture] - [UmbracoTest(WithApplication = true)] - public class AutoMapperTests : UmbracoTestBase - { - protected override void Compose() - { - base.Compose(); - - var manifestBuilder = new ManifestParser( - AppCaches.Disabled, - new ManifestValueValidatorCollection(Enumerable.Empty()), - Composition.Logger) - { - Path = TestHelper.CurrentAssemblyDirectory - }; - Composition.RegisterUnique(_ => manifestBuilder); - - Func> typeListProducerList = Enumerable.Empty; - Composition.WithCollectionBuilder() - .Clear() - .Add(typeListProducerList); - } - - [Test] - public void AssertConfigurationIsValid() - { - var profiles = Factory.GetAllInstances().ToArray(); - - var config = new MapperConfiguration(cfg => - { - foreach (var profile in profiles) - cfg.AddProfile(profile); - }); - - // validate each profile (better granularity for error reports) - - Console.WriteLine("Validate each profile:"); - foreach (var profile in profiles) - { - try - { - config.AssertConfigurationIsValid(profile.GetType().FullName); - //Console.WriteLine("OK " + profile.GetType().FullName); - } - catch (Exception e) - { - Console.WriteLine("KO " + profile.GetType().FullName); - Console.WriteLine(e); - } - } - - Console.WriteLine(); - Console.WriteLine("Validate each profile and throw:"); - foreach (var profile in profiles) - { - try - { - config.AssertConfigurationIsValid(profile.GetType().FullName); - } - catch - { - Console.WriteLine("KO " + profile.GetType().FullName); - throw; - } - } - - // validate the global config - Console.WriteLine(); - Console.WriteLine("Validate global config:"); - config.AssertConfigurationIsValid(); - } - } -} diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 2e6cfaae89..21180ce51b 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -11,7 +10,6 @@ using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Models.Mapping @@ -28,22 +26,6 @@ namespace Umbraco.Tests.Models.Mapping private readonly Mock _fileService = new Mock(); private Mock _editorsMock; - public override void SetUp() - { - base.SetUp(); - - // FIXME: are we initializing mappers that... have already been? - Mapper.Reset(); - Mapper.Initialize(configuration => - { - //initialize our content type mapper - var profile1 = new ContentTypeMapperProfile(_editorsMock.Object, _dataTypeService.Object, _fileService.Object, _contentTypeService.Object, Mock.Of(), Mock.Of()); - configuration.AddProfile(profile1); - var profile2 = new EntityMapperProfile(); - configuration.AddProfile(profile2); - }); - } - protected override void Compose() { base.Compose(); diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 7ebdfabbb0..6a4054d5ae 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Linq; -using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -22,7 +21,7 @@ using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Models.Mapping { [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class ContentWebModelMappingTests : TestWithDatabaseBase { private IContentTypeService _contentTypeService; diff --git a/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs index bdab736cd1..797fce2bd1 100644 --- a/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using AutoMapper; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -10,7 +9,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Models.Mapping { [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class UserModelMapperTests : TestWithDatabaseBase { [Test] diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index 5569ecfa60..8d64a6d28f 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -275,7 +275,7 @@ namespace Umbraco.Tests.Models // can publish value // and get edited and published values - Assert.IsTrue(content.PublishCulture()); + Assert.IsTrue(content.PublishCulture(CultureImpact.All)); Assert.AreEqual("a", content.GetValue("prop")); Assert.AreEqual("a", content.GetValue("prop", published: true)); @@ -305,9 +305,9 @@ namespace Umbraco.Tests.Models // can publish value // and get edited and published values - Assert.IsFalse(content.PublishCulture(langFr)); // no name + Assert.IsFalse(content.PublishCulture(CultureImpact.Explicit(langFr, false))); // no name content.SetCultureName("name-fr", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); + Assert.IsTrue(content.PublishCulture(CultureImpact.Explicit(langFr, false))); Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); @@ -321,7 +321,7 @@ namespace Umbraco.Tests.Models Assert.IsNull(content.GetValue("prop", langFr, published: true)); // can publish all - Assert.IsTrue(content.PublishCulture("*")); + Assert.IsTrue(content.PublishCulture(CultureImpact.All)); Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); @@ -331,14 +331,14 @@ namespace Umbraco.Tests.Models content.UnpublishCulture(langFr); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.IsNull(content.GetValue("prop", langFr, published: true)); - content.PublishCulture(langFr); + Assert.IsTrue(content.PublishCulture(CultureImpact.Explicit(langFr, false))); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); content.UnpublishCulture(); // clears invariant props if any Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); - content.PublishCulture(); // publishes invariant props if any + Assert.IsTrue(content.PublishCulture(CultureImpact.All)); // publishes invariant props if any Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); @@ -384,15 +384,20 @@ namespace Umbraco.Tests.Models content.SetCultureName("hello", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) - Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop1 is mandatory + //for this test we'll make the french culture the default one - this is needed for publishing invariant property values + var langFrImpact = CultureImpact.Explicit(langFr, true); + + Assert.IsTrue(content.PublishCulture(langFrImpact)); // succeeds because names are ok (not validating properties here) + Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact));// fails because prop1 is mandatory content.SetValue("prop1", "a", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) - Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop2 is mandatory and invariant + Assert.IsTrue(content.PublishCulture(langFrImpact)); // succeeds because names are ok (not validating properties here) + // fails because prop2 is mandatory and invariant and the item isn't published. + // Invariant is validated against the default language except when there isn't a published version, in that case it's always validated. + Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact)); content.SetValue("prop2", "x"); - Assert.IsTrue(content.PublishCulture(langFr)); // still ok... - Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// now it's ok + Assert.IsTrue(content.PublishCulture(langFrImpact)); // still ok... + Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact));// now it's ok Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true)); Assert.AreEqual("x", content.GetValue("prop2", published: true)); @@ -423,12 +428,12 @@ namespace Umbraco.Tests.Models content.SetValue("prop", "a-es", langEs); // cannot publish without a name - Assert.IsFalse(content.PublishCulture(langFr)); + Assert.IsFalse(content.PublishCulture(CultureImpact.Explicit(langFr, false))); // works with a name // and then FR is available, and published content.SetCultureName("name-fr", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); + Assert.IsTrue(content.PublishCulture(CultureImpact.Explicit(langFr, false))); // now UK is available too content.SetCultureName("name-uk", langUk); diff --git a/src/Umbraco.Tests/Persistence/Mappers/ContentMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/ContentMapperTest.cs index a87aca1ad7..9ad77c0246 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/ContentMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/ContentMapperTest.cs @@ -2,38 +2,37 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class ContentMapperTest + public class ContentMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { - var column = new ContentMapper().Map(new SqlCeSyntaxProvider(), nameof(Content.Id)); + var column = new ContentMapper(MockSqlContext(), CreateMaps()).Map(nameof(Content.Id)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Node}].[id]")); } [Test] public void Can_Map_Trashed_Property() { - var column = new ContentMapper().Map(new SqlCeSyntaxProvider(), nameof(Content.Trashed)); + var column = new ContentMapper(MockSqlContext(), CreateMaps()).Map(nameof(Content.Trashed)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Node}].[trashed]")); } [Test] public void Can_Map_Published_Property() { - var column = new ContentMapper().Map(new SqlCeSyntaxProvider(), nameof(Content.Published)); + var column = new ContentMapper(MockSqlContext(), CreateMaps()).Map(nameof(Content.Published)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Document}].[published]")); } [Test] public void Can_Map_Version_Property() { - var column = new ContentMapper().Map(new SqlCeSyntaxProvider(), nameof(Content.VersionId)); + var column = new ContentMapper(MockSqlContext(), CreateMaps()).Map(nameof(Content.VersionId)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.ContentVersion}].[id]")); } } diff --git a/src/Umbraco.Tests/Persistence/Mappers/ContentTypeMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/ContentTypeMapperTest.cs index 35efce6dbe..dcd064e862 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/ContentTypeMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/ContentTypeMapperTest.cs @@ -1,18 +1,17 @@ using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class ContentTypeMapperTest + public class ContentTypeMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { // Act - string column = new ContentTypeMapper().Map(new SqlCeSyntaxProvider(), "Id"); + string column = new ContentTypeMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[umbracoNode].[id]")); @@ -23,7 +22,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new ContentTypeMapper().Map(new SqlCeSyntaxProvider(), "Name"); + string column = new ContentTypeMapper(MockSqlContext(), CreateMaps()).Map("Name"); // Assert Assert.That(column, Is.EqualTo("[umbracoNode].[text]")); @@ -34,7 +33,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new ContentTypeMapper().Map(new SqlCeSyntaxProvider(), "Thumbnail"); + string column = new ContentTypeMapper(MockSqlContext(), CreateMaps()).Map("Thumbnail"); // Assert Assert.That(column, Is.EqualTo("[cmsContentType].[thumbnail]")); @@ -45,7 +44,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new ContentTypeMapper().Map(new SqlCeSyntaxProvider(), "Description"); + string column = new ContentTypeMapper(MockSqlContext(), CreateMaps()).Map("Description"); // Assert Assert.That(column, Is.EqualTo("[cmsContentType].[description]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/DataTypeMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/DataTypeMapperTest.cs index a65464a629..2ae8f755a2 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/DataTypeMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/DataTypeMapperTest.cs @@ -6,14 +6,14 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class DataTypeMapperTest + public class DataTypeMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { // Act - string column = new DataTypeMapper().Map(new SqlCeSyntaxProvider(), "Id"); + string column = new DataTypeMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[umbracoNode].[id]")); @@ -24,7 +24,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new DataTypeMapper().Map(new SqlCeSyntaxProvider(), "Key"); + string column = new DataTypeMapper(MockSqlContext(), CreateMaps()).Map("Key"); // Assert Assert.That(column, Is.EqualTo("[umbracoNode].[uniqueId]")); @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new DataTypeMapper().Map(new SqlCeSyntaxProvider(), "DatabaseType"); + string column = new DataTypeMapper(MockSqlContext(), CreateMaps()).Map("DatabaseType"); // Assert Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.DataType}].[dbType]")); @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new DataTypeMapper().Map(new SqlCeSyntaxProvider(), "EditorAlias"); + string column = new DataTypeMapper(MockSqlContext(), CreateMaps()).Map("EditorAlias"); // Assert Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.DataType}].[propertyEditorAlias]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/DictionaryMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/DictionaryMapperTest.cs index bd4a1ee969..cb4e8dc534 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/DictionaryMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/DictionaryMapperTest.cs @@ -5,14 +5,14 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class DictionaryMapperTest + public class DictionaryMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { // Act - string column = new DictionaryMapper().Map(new SqlCeSyntaxProvider(), "Id"); + string column = new DictionaryMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[cmsDictionary].[pk]")); @@ -23,7 +23,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new DictionaryMapper().Map(new SqlCeSyntaxProvider(), "Key"); + string column = new DictionaryMapper(MockSqlContext(), CreateMaps()).Map("Key"); // Assert Assert.That(column, Is.EqualTo("[cmsDictionary].[id]")); @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new DictionaryMapper().Map(new SqlCeSyntaxProvider(), "ItemKey"); + string column = new DictionaryMapper(MockSqlContext(), CreateMaps()).Map("ItemKey"); // Assert Assert.That(column, Is.EqualTo("[cmsDictionary].[key]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/DictionaryTranslationMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/DictionaryTranslationMapperTest.cs index c3e02105d6..c40ddb0cdf 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/DictionaryTranslationMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/DictionaryTranslationMapperTest.cs @@ -1,18 +1,17 @@ using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class DictionaryTranslationMapperTest + public class DictionaryTranslationMapperTest : MapperTestBase { [Test] public void Can_Map_Key_Property() { // Act - string column = new DictionaryTranslationMapper().Map(new SqlCeSyntaxProvider(), "Key"); + string column = new DictionaryTranslationMapper(MockSqlContext(), CreateMaps()).Map("Key"); // Assert Assert.That(column, Is.EqualTo("[cmsLanguageText].[UniqueId]")); @@ -23,7 +22,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new DictionaryTranslationMapper().Map(new SqlCeSyntaxProvider(), "Language"); + string column = new DictionaryTranslationMapper(MockSqlContext(), CreateMaps()).Map("Language"); // Assert Assert.That(column, Is.EqualTo("[cmsLanguageText].[languageId]")); @@ -34,7 +33,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new DictionaryTranslationMapper().Map(new SqlCeSyntaxProvider(), "Value"); + string column = new DictionaryTranslationMapper(MockSqlContext(), CreateMaps()).Map("Value"); // Assert Assert.That(column, Is.EqualTo("[cmsLanguageText].[value]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/LanguageMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/LanguageMapperTest.cs index 39d731c72d..5db8a991d6 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/LanguageMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/LanguageMapperTest.cs @@ -5,14 +5,14 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class LanguageMapperTest + public class LanguageMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { // Act - string column = new LanguageMapper().Map(new SqlCeSyntaxProvider(), "Id"); + string column = new LanguageMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[umbracoLanguage].[id]")); @@ -23,7 +23,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new LanguageMapper().Map(new SqlCeSyntaxProvider(), "IsoCode"); + string column = new LanguageMapper(MockSqlContext(), CreateMaps()).Map("IsoCode"); // Assert Assert.That(column, Is.EqualTo("[umbracoLanguage].[languageISOCode]")); @@ -33,7 +33,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_CultureName_Property() { // Act - string column = new LanguageMapper().Map(new SqlCeSyntaxProvider(), "CultureName"); + string column = new LanguageMapper(MockSqlContext(), CreateMaps()).Map("CultureName"); // Assert Assert.That(column, Is.EqualTo("[umbracoLanguage].[languageCultureName]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/MapperTestBase.cs b/src/Umbraco.Tests/Persistence/Mappers/MapperTestBase.cs new file mode 100644 index 0000000000..d4a61155a9 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Mappers/MapperTestBase.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Concurrent; +using Moq; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Tests.Persistence.Mappers +{ + public class MapperTestBase + { + protected Lazy MockSqlContext() + { + var sqlContext = Mock.Of(); + var syntax = new SqlCeSyntaxProvider(); + Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(syntax); + return new Lazy(() => sqlContext); + } + + protected ConcurrentDictionary> CreateMaps() + => new ConcurrentDictionary>(); + } +} diff --git a/src/Umbraco.Tests/Persistence/Mappers/MediaMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/MediaMapperTest.cs index 0d6cc12d7c..9c7b0729b6 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/MediaMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/MediaMapperTest.cs @@ -7,33 +7,33 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class MediaMapperTest + public class MediaMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { - var column = new MediaMapper().Map(new SqlCeSyntaxProvider(), nameof(Media.Id)); + var column = new MediaMapper(MockSqlContext(), CreateMaps()).Map(nameof(Media.Id)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Node}].[id]")); } [Test] public void Can_Map_Trashed_Property() { - var column = new MediaMapper().Map(new SqlCeSyntaxProvider(), nameof(Media.Trashed)); + var column = new MediaMapper(MockSqlContext(), CreateMaps()).Map(nameof(Media.Trashed)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Node}].[trashed]")); } [Test] public void Can_Map_UpdateDate_Property() { - var column = new MediaMapper().Map(new SqlCeSyntaxProvider(), nameof(Media.UpdateDate)); + var column = new MediaMapper(MockSqlContext(), CreateMaps()).Map(nameof(Media.UpdateDate)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.ContentVersion}].[versionDate]")); } [Test] public void Can_Map_Version_Property() { - var column = new MediaMapper().Map(new SqlCeSyntaxProvider(), nameof(Media.VersionId)); + var column = new MediaMapper(MockSqlContext(), CreateMaps()).Map(nameof(Media.VersionId)); Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.ContentVersion}].[id]")); } } diff --git a/src/Umbraco.Tests/Persistence/Mappers/PropertyGroupMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/PropertyGroupMapperTest.cs index 84ad2e85dc..780ae482cb 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/PropertyGroupMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/PropertyGroupMapperTest.cs @@ -1,20 +1,16 @@ using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class PropertyGroupMapperTest + public class PropertyGroupMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { - // Arrange - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - // Act - string column = new PropertyGroupMapper().Map(sqlSyntaxProvider, "Id"); + string column = new PropertyGroupMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[cmsPropertyTypeGroup].[id]")); @@ -23,11 +19,8 @@ namespace Umbraco.Tests.Persistence.Mappers [Test] public void Can_Map_SortOrder_Property() { - // Arrange - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - // Act - string column = new PropertyGroupMapper().Map(sqlSyntaxProvider, "SortOrder"); + string column = new PropertyGroupMapper(MockSqlContext(), CreateMaps()).Map("SortOrder"); // Assert Assert.That(column, Is.EqualTo("[cmsPropertyTypeGroup].[sortorder]")); @@ -36,11 +29,8 @@ namespace Umbraco.Tests.Persistence.Mappers [Test] public void Can_Map_Name_Property() { - // Arrange - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - // Act - string column = new PropertyGroupMapper().Map(sqlSyntaxProvider, "Name"); + string column = new PropertyGroupMapper(MockSqlContext(), CreateMaps()).Map("Name"); // Assert Assert.That(column, Is.EqualTo("[cmsPropertyTypeGroup].[text]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/PropertyTypeMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/PropertyTypeMapperTest.cs index 3fa8af510e..ab76fa211b 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/PropertyTypeMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/PropertyTypeMapperTest.cs @@ -6,13 +6,13 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class PropertyTypeMapperTest + public class PropertyTypeMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { // Act - string column = new PropertyTypeMapper().Map(new SqlCeSyntaxProvider(), "Id"); + string column = new PropertyTypeMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[cmsPropertyType].[id]")); @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_Alias_Property() { // Act - string column = new PropertyTypeMapper().Map(new SqlCeSyntaxProvider(), "Alias"); + string column = new PropertyTypeMapper(MockSqlContext(), CreateMaps()).Map("Alias"); // Assert Assert.That(column, Is.EqualTo("[cmsPropertyType].[Alias]")); @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_DataTypeDefinitionId_Property() { // Act - string column = new PropertyTypeMapper().Map(new SqlCeSyntaxProvider(), "DataTypeId"); + string column = new PropertyTypeMapper(MockSqlContext(), CreateMaps()).Map("DataTypeId"); // Assert Assert.That(column, Is.EqualTo("[cmsPropertyType].[dataTypeId]")); @@ -42,7 +42,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_SortOrder_Property() { // Act - string column = new PropertyTypeMapper().Map(new SqlCeSyntaxProvider(), "SortOrder"); + string column = new PropertyTypeMapper(MockSqlContext(), CreateMaps()).Map("SortOrder"); // Assert Assert.That(column, Is.EqualTo("[cmsPropertyType].[sortOrder]")); @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_PropertyEditorAlias_Property() { // Act - string column = new PropertyTypeMapper().Map(new SqlCeSyntaxProvider(), "PropertyEditorAlias"); + string column = new PropertyTypeMapper(MockSqlContext(), CreateMaps()).Map("PropertyEditorAlias"); // Assert Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.DataType}].[propertyEditorAlias]")); @@ -62,7 +62,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_DataTypeDatabaseType_Property() { // Act - string column = new PropertyTypeMapper().Map(new SqlCeSyntaxProvider(), "ValueStorageType"); + string column = new PropertyTypeMapper(MockSqlContext(), CreateMaps()).Map("ValueStorageType"); // Assert Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.DataType}].[dbType]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/RelationMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/RelationMapperTest.cs index 7a4a924a76..630c263924 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/RelationMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/RelationMapperTest.cs @@ -5,13 +5,13 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class RelationMapperTest + public class RelationMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { // Act - string column = new RelationMapper().Map(new SqlCeSyntaxProvider(), "Id"); + string column = new RelationMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelation].[id]")); @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_ChildId_Property() { // Act - string column = new RelationMapper().Map(new SqlCeSyntaxProvider(), "ChildId"); + string column = new RelationMapper(MockSqlContext(), CreateMaps()).Map("ChildId"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelation].[childId]")); @@ -31,7 +31,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_Datetime_Property() { // Act - string column = new RelationMapper().Map(new SqlCeSyntaxProvider(), "CreateDate"); + string column = new RelationMapper(MockSqlContext(), CreateMaps()).Map("CreateDate"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelation].[datetime]")); @@ -41,7 +41,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_Comment_Property() { // Act - string column = new RelationMapper().Map(new SqlCeSyntaxProvider(), "Comment"); + string column = new RelationMapper(MockSqlContext(), CreateMaps()).Map("Comment"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelation].[comment]")); @@ -51,7 +51,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_RelationType_Property() { // Act - string column = new RelationMapper().Map(new SqlCeSyntaxProvider(), "RelationTypeId"); + string column = new RelationMapper(MockSqlContext(), CreateMaps()).Map("RelationTypeId"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelation].[relType]")); diff --git a/src/Umbraco.Tests/Persistence/Mappers/RelationTypeMapperTest.cs b/src/Umbraco.Tests/Persistence/Mappers/RelationTypeMapperTest.cs index 920cb06668..32e4f307c9 100644 --- a/src/Umbraco.Tests/Persistence/Mappers/RelationTypeMapperTest.cs +++ b/src/Umbraco.Tests/Persistence/Mappers/RelationTypeMapperTest.cs @@ -5,13 +5,13 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Mappers { [TestFixture] - public class RelationTypeMapperTest + public class RelationTypeMapperTest : MapperTestBase { [Test] public void Can_Map_Id_Property() { // Act - string column = new RelationTypeMapper().Map(new SqlCeSyntaxProvider(), "Id"); + string column = new RelationTypeMapper(MockSqlContext(), CreateMaps()).Map("Id"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelationType].[id]")); @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Mappers public void Can_Map_Alias_Property() { // Act - string column = new RelationTypeMapper().Map(new SqlCeSyntaxProvider(), "Alias"); + string column = new RelationTypeMapper(MockSqlContext(), CreateMaps()).Map("Alias"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelationType].[alias]")); @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new RelationTypeMapper().Map(new SqlCeSyntaxProvider(), "ChildObjectType"); + string column = new RelationTypeMapper(MockSqlContext(), CreateMaps()).Map("ChildObjectType"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelationType].[childObjectType]")); @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Persistence.Mappers { // Act - string column = new RelationTypeMapper().Map(new SqlCeSyntaxProvider(), "IsBidirectional"); + string column = new RelationTypeMapper(MockSqlContext(), CreateMaps()).Map("IsBidirectional"); // Assert Assert.That(column, Is.EqualTo("[umbracoRelationType].[dual]")); diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs index f558a64499..5372a12ac2 100644 --- a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs +++ b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs @@ -181,13 +181,13 @@ namespace Umbraco.Tests.Persistence.NPocoTests contentTypeService.Save(contentType); var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content1.PublishCulture(); + content1.PublishCulture(CultureImpact.Invariant); contentService.SaveAndPublish(content1); id2 = content1.Id; var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.PublishCulture(); + content2.PublishCulture(CultureImpact.Invariant); contentService.SaveAndPublish(content2); id3 = content2.Id; diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs index ce6447f4b5..fcf64641c8 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs @@ -1,200 +1,199 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; +// fixme - does it make any sense to keep these tests here? +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using NPoco; +//using NUnit.Framework; +//using Umbraco.Core; +//using Umbraco.Core.Models; +//using Umbraco.Core.Persistence.Dtos; +//using Umbraco.Core.Persistence.Repositories; +//using Umbraco.Core.Persistence.Repositories.Implement; +//using Umbraco.Tests.TestHelpers; +//using Umbraco.Tests.Testing; -namespace Umbraco.Tests.Persistence.Querying -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class ContentTypeSqlMappingTests : TestWithDatabaseBase - { - [Test] - public void Can_Map_Content_Type_Templates_And_Allowed_Types() - { - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; - IContentType[] contentTypes; +//namespace Umbraco.Tests.Persistence.Querying +//{ +// [TestFixture] +// [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +// public class ContentTypeSqlMappingTests : TestWithDatabaseBase +// { +// [Test] +// public void Can_Map_Content_Type_Templates_And_Allowed_Types() +// { +// IDictionary.AssociatedTemplate>> allAssociatedTemplates; +// IDictionary> allParentContentTypeIds; +// IContentType[] contentTypes; - using (var scope = ScopeProvider.CreateScope()) - { - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55554, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55554", SortOrder = 1, UniqueId = new Guid("87D1EAB6-AB27-4852-B3DF-DE8DBA4A1AA0"), Text = "Template 1", NodeObjectType = Constants.ObjectTypes.Template, CreateDate = DateTime.Now }); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55555, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55555", SortOrder = 1, UniqueId = new Guid("3390BDF4-C974-4211-AA95-3812A8CE7C46"), Text = "Template 2", NodeObjectType = Constants.ObjectTypes.Template, CreateDate = DateTime.Now }); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99997, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99997", SortOrder = 0, UniqueId = new Guid("BB3241D5-6842-4EFA-A82A-5F56885CF528"), Text = "Test Content Type 1", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99998, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99998", SortOrder = 0, UniqueId = new Guid("EEA66B06-302E-49BA-A8B2-EDF07248BC59"), Text = "Test Content Type 2", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("C45CC083-BB27-4C1C-B448-6F703CC9B799"), Text = "Test Content Type 2", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); +// using (var scope = ScopeProvider.CreateScope()) +// { +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55554, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55554", SortOrder = 1, UniqueId = new Guid("87D1EAB6-AB27-4852-B3DF-DE8DBA4A1AA0"), Text = "Template 1", NodeObjectType = Constants.ObjectTypes.Template, CreateDate = DateTime.Now }); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55555, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55555", SortOrder = 1, UniqueId = new Guid("3390BDF4-C974-4211-AA95-3812A8CE7C46"), Text = "Template 2", NodeObjectType = Constants.ObjectTypes.Template, CreateDate = DateTime.Now }); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99997, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99997", SortOrder = 0, UniqueId = new Guid("BB3241D5-6842-4EFA-A82A-5F56885CF528"), Text = "Test Content Type 1", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99998, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99998", SortOrder = 0, UniqueId = new Guid("EEA66B06-302E-49BA-A8B2-EDF07248BC59"), Text = "Test Content Type 2", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("C45CC083-BB27-4C1C-B448-6F703CC9B799"), Text = "Test Content Type 2", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsTemplate")))); - scope.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55554, Alias = "testTemplate1", PrimaryKey = 22221}); - scope.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55555, Alias = "testTemplate2", PrimaryKey = 22222 }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsTemplate")))); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsTemplate")))); +// scope.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55554, Alias = "testTemplate1", PrimaryKey = 22221}); +// scope.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55555, Alias = "testTemplate2", PrimaryKey = 22222 }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsTemplate")))); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88887, NodeId = 99997, Alias = "TestContentType1", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); - scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99998, Alias = "TestContentType2", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); - scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88889, NodeId = 99999, Alias = "TestContentType3", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); +// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88887, NodeId = 99997, Alias = "TestContentType1", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); +// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99998, Alias = "TestContentType2", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); +// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88889, NodeId = 99999, Alias = "TestContentType3", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - scope.Database.Insert(new ContentTypeTemplateDto { ContentTypeNodeId = 99997, IsDefault = true, TemplateNodeId = 55555 }); - scope.Database.Insert(new ContentTypeTemplateDto { ContentTypeNodeId = 99997, IsDefault = false, TemplateNodeId = 55554 }); +// scope.Database.Insert(new ContentTypeTemplateDto { ContentTypeNodeId = 99997, IsDefault = true, TemplateNodeId = 55555 }); +// scope.Database.Insert(new ContentTypeTemplateDto { ContentTypeNodeId = 99997, IsDefault = false, TemplateNodeId = 55554 }); - scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99998, Id = 99997, SortOrder = 1 }); - scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99999, Id = 99997, SortOrder = 2}); +// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99998, Id = 99997, SortOrder = 1 }); +// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99999, Id = 99997, SortOrder = 2}); - scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99999, ParentId = 99997}); - scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99998, ParentId = 99997 }); +// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99999, ParentId = 99997}); +// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99998, ParentId = 99997 }); - contentTypes = ContentTypeRepository.ContentTypeQueryMapper.MapContentTypes( - scope.Database, SqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) - .Where(x => new[] { 99997, 99998 }.Contains(x.Id)) - .ToArray(); +// contentTypes = ContentTypeQueryMapper.GetContentTypes( +// scope.Database, out allAssociatedTemplates, out allParentContentTypeIds) +// .Where(x => new[] { 99997, 99998 }.Contains(x.Id)) +// .ToArray(); - scope.Complete(); - } +// scope.Complete(); +// } - var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); - Assert.IsNotNull(contentType1); +// var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); +// Assert.IsNotNull(contentType1); - var associatedTemplates1 = allAssociatedTemplates[contentType1.Id]; - var parentContentTypes1 = allParentContentTypeIds[contentType1.Id]; +// var associatedTemplates1 = allAssociatedTemplates[contentType1.Id]; +// var parentContentTypes1 = allParentContentTypeIds[contentType1.Id]; - Assert.AreEqual(2, contentType1.AllowedContentTypes.Count()); - Assert.AreEqual(2, associatedTemplates1.Count()); - Assert.AreEqual(0, parentContentTypes1.Count()); +// Assert.AreEqual(2, contentType1.AllowedContentTypes.Count()); +// Assert.AreEqual(2, associatedTemplates1.Count()); +// Assert.AreEqual(0, parentContentTypes1.Count()); - var contentType2 = contentTypes.SingleOrDefault(x => x.Id == 99998); - Assert.IsNotNull(contentType2); +// var contentType2 = contentTypes.SingleOrDefault(x => x.Id == 99998); +// Assert.IsNotNull(contentType2); - var associatedTemplates2 = allAssociatedTemplates[contentType2.Id]; - var parentContentTypes2 = allParentContentTypeIds[contentType2.Id]; +// var associatedTemplates2 = allAssociatedTemplates[contentType2.Id]; +// var parentContentTypes2 = allParentContentTypeIds[contentType2.Id]; - Assert.AreEqual(0, contentType2.AllowedContentTypes.Count()); - Assert.AreEqual(0, associatedTemplates2.Count()); - Assert.AreEqual(1, parentContentTypes2.Count()); - } +// Assert.AreEqual(0, contentType2.AllowedContentTypes.Count()); +// Assert.AreEqual(0, associatedTemplates2.Count()); +// Assert.AreEqual(1, parentContentTypes2.Count()); +// } - [Test] - public void Can_Map_Media_Type_And_Allowed_Types() - { - IDictionary> allParentContentTypeIds; - IMediaType[] contentTypes; +// [Test] +// public void Can_Map_Media_Type_And_Allowed_Types() +// { +// IDictionary> allParentContentTypeIds; +// IMediaType[] contentTypes; - using (var scope = ScopeProvider.CreateScope()) - { - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99997, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99997", SortOrder = 0, UniqueId = new Guid("BB3241D5-6842-4EFA-A82A-5F56885CF528"), Text = "Test Media Type 1", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99998, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99998", SortOrder = 0, UniqueId = new Guid("EEA66B06-302E-49BA-A8B2-EDF07248BC59"), Text = "Test Media Type 2", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("C45CC083-BB27-4C1C-B448-6F703CC9B799"), Text = "Test Media Type 2", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); +// using (var scope = ScopeProvider.CreateScope()) +// { +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99997, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99997", SortOrder = 0, UniqueId = new Guid("BB3241D5-6842-4EFA-A82A-5F56885CF528"), Text = "Test Media Type 1", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99998, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99998", SortOrder = 0, UniqueId = new Guid("EEA66B06-302E-49BA-A8B2-EDF07248BC59"), Text = "Test Media Type 2", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("C45CC083-BB27-4C1C-B448-6F703CC9B799"), Text = "Test Media Type 2", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88887, NodeId = 99997, Alias = "TestContentType1", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); - scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99998, Alias = "TestContentType2", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); - scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88889, NodeId = 99999, Alias = "TestContentType3", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); +// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88887, NodeId = 99997, Alias = "TestContentType1", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); +// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99998, Alias = "TestContentType2", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); +// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88889, NodeId = 99999, Alias = "TestContentType3", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99998, Id = 99997, SortOrder = 1 }); - scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99999, Id = 99997, SortOrder = 2 }); +// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99998, Id = 99997, SortOrder = 1 }); +// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99999, Id = 99997, SortOrder = 2 }); - scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99999, ParentId = 99997 }); - scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99998, ParentId = 99997 }); +// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99999, ParentId = 99997 }); +// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99998, ParentId = 99997 }); - contentTypes = ContentTypeRepository.ContentTypeQueryMapper.MapMediaTypes( - scope.Database, SqlSyntax, out allParentContentTypeIds) - .Where(x => (new[] { 99997, 99998 }).Contains(x.Id)) - .ToArray(); +// contentTypes = ContentTypeQueryMapper.MapMediaTypes( +// scope.Database, SqlSyntax, out allParentContentTypeIds) +// .Where(x => (new[] { 99997, 99998 }).Contains(x.Id)) +// .ToArray(); - scope.Complete(); - } +// scope.Complete(); +// } - var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); - Assert.IsNotNull(contentType1); +// var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); +// Assert.IsNotNull(contentType1); - var parentContentTypes1 = allParentContentTypeIds[contentType1.Id]; +// var parentContentTypes1 = allParentContentTypeIds[contentType1.Id]; - Assert.AreEqual(2, contentType1.AllowedContentTypes.Count()); - Assert.AreEqual(0, parentContentTypes1.Count()); +// Assert.AreEqual(2, contentType1.AllowedContentTypes.Count()); +// Assert.AreEqual(0, parentContentTypes1.Count()); - var contentType2 = contentTypes.SingleOrDefault(x => x.Id == 99998); - Assert.IsNotNull(contentType2); +// var contentType2 = contentTypes.SingleOrDefault(x => x.Id == 99998); +// Assert.IsNotNull(contentType2); - var parentContentTypes2 = allParentContentTypeIds[contentType2.Id]; +// var parentContentTypes2 = allParentContentTypeIds[contentType2.Id]; - Assert.AreEqual(0, contentType2.AllowedContentTypes.Count()); - Assert.AreEqual(1, parentContentTypes2.Count()); +// Assert.AreEqual(0, contentType2.AllowedContentTypes.Count()); +// Assert.AreEqual(1, parentContentTypes2.Count()); - } +// } - [Test] - public void Can_Map_All_Property_Groups_And_Types() - { - IDictionary allPropTypeCollection; - IDictionary allPropGroupCollection; +// [Test] +// public void Can_Map_All_Property_Groups_And_Types() +// { +// IDictionary allPropTypeCollection; +// IDictionary allPropGroupCollection; - using (var scope = ScopeProvider.CreateScope()) - { - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55555, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55555", SortOrder = 1, UniqueId = new Guid("3390BDF4-C974-4211-AA95-3812A8CE7C46"), Text = "Test Data Type", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("129241F0-D24E-4FC3-92D1-BC2D48B7C431"), Text = "Test Content Type", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); +// using (var scope = ScopeProvider.CreateScope()) +// { +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55555, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55555", SortOrder = 1, UniqueId = new Guid("3390BDF4-C974-4211-AA95-3812A8CE7C46"), Text = "Test Data Type", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); +// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("129241F0-D24E-4FC3-92D1-BC2D48B7C431"), Text = "Test Content Type", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 55555, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); +// scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 55555, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99999, Alias = "TestContentType", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); +// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99999, Alias = "TestContentType", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsPropertyTypeGroup")))); - scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77776, UniqueId = 77776.ToGuid(), ContentTypeNodeId = 99999, Text = "Group1", SortOrder = 1 }); - scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77777, UniqueId = 77777.ToGuid(), ContentTypeNodeId = 99999, Text = "Group2", SortOrder = 2 }); - scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77778, UniqueId = 77778.ToGuid(), ContentTypeNodeId = 99999, Text = "Group3", SortOrder = 3 }); - scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77779, UniqueId = 77779.ToGuid(), ContentTypeNodeId = 99999, Text = "Group4", SortOrder = 4 }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsPropertyTypeGroup")))); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsPropertyTypeGroup")))); +// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77776, UniqueId = 77776.ToGuid(), ContentTypeNodeId = 99999, Text = "Group1", SortOrder = 1 }); +// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77777, UniqueId = 77777.ToGuid(), ContentTypeNodeId = 99999, Text = "Group2", SortOrder = 2 }); +// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77778, UniqueId = 77778.ToGuid(), ContentTypeNodeId = 99999, Text = "Group3", SortOrder = 3 }); +// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77779, UniqueId = 77779.ToGuid(), ContentTypeNodeId = 99999, Text = "Group4", SortOrder = 4 }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsPropertyTypeGroup")))); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsPropertyType")))); - scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66662, UniqueId = 66662.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77776, Alias = "property1", Name = "Property 1", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66663, UniqueId = 66663.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77776, Alias = "property2", Name = "Property 2", SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); - scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66664, UniqueId = 66664.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77777, Alias = "property3", Name = "Property 3", SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); - scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66665, UniqueId = 66665.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77777, Alias = "property4", Name = "Property 4", SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); - scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66666, UniqueId = 66666.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = null, Alias = "property5", Name = "Property 5", SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); - scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66667, UniqueId = 66667.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77778, Alias = "property6", Name = "Property 6", SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); - scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66668, UniqueId = 66668.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77778, Alias = "property7", Name = "Property 7", SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); - scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsPropertyType")))); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsPropertyType")))); +// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66662, UniqueId = 66662.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77776, Alias = "property1", Name = "Property 1", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); +// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66663, UniqueId = 66663.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77776, Alias = "property2", Name = "Property 2", SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); +// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66664, UniqueId = 66664.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77777, Alias = "property3", Name = "Property 3", SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); +// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66665, UniqueId = 66665.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77777, Alias = "property4", Name = "Property 4", SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); +// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66666, UniqueId = 66666.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = null, Alias = "property5", Name = "Property 5", SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); +// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66667, UniqueId = 66667.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77778, Alias = "property6", Name = "Property 6", SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); +// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66668, UniqueId = 66668.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77778, Alias = "property7", Name = "Property 7", SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); +// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsPropertyType")))); - ContentTypeRepository.ContentTypeQueryMapper.MapGroupsAndProperties(new[] { 99999 }, scope.Database, SqlSyntax, true, out allPropTypeCollection, out allPropGroupCollection); +// ContentTypeQueryMapper.MapGroupsAndProperties_(new[] { 99999 }, scope.Database, SqlSyntax, true, out allPropTypeCollection, out allPropGroupCollection); - scope.Complete(); - } +// scope.Complete(); +// } - var propGroupCollection = allPropGroupCollection[99999]; - var propTypeCollection = allPropTypeCollection[99999]; +// var propGroupCollection = allPropGroupCollection[99999]; +// var propTypeCollection = allPropTypeCollection[99999]; - Assert.AreEqual(4, propGroupCollection.Count); - Assert.AreEqual(2, propGroupCollection["Group1"].PropertyTypes.Count); - Assert.IsTrue(propGroupCollection["Group1"].PropertyTypes.Contains("property1")); - Assert.IsTrue(propGroupCollection["Group1"].PropertyTypes.Contains("property2")); - Assert.AreEqual(2, propGroupCollection["Group2"].PropertyTypes.Count); - Assert.IsTrue(propGroupCollection["Group2"].PropertyTypes.Contains("property3")); - Assert.IsTrue(propGroupCollection["Group2"].PropertyTypes.Contains("property4")); - Assert.AreEqual(2, propGroupCollection["Group3"].PropertyTypes.Count); - Assert.IsTrue(propGroupCollection["Group3"].PropertyTypes.Contains("property6")); - Assert.IsTrue(propGroupCollection["Group3"].PropertyTypes.Contains("property7")); - Assert.AreEqual(0, propGroupCollection["Group4"].PropertyTypes.Count); +// Assert.AreEqual(4, propGroupCollection.Count); +// Assert.AreEqual(2, propGroupCollection["Group1"].PropertyTypes.Count); +// Assert.IsTrue(propGroupCollection["Group1"].PropertyTypes.Contains("property1")); +// Assert.IsTrue(propGroupCollection["Group1"].PropertyTypes.Contains("property2")); +// Assert.AreEqual(2, propGroupCollection["Group2"].PropertyTypes.Count); +// Assert.IsTrue(propGroupCollection["Group2"].PropertyTypes.Contains("property3")); +// Assert.IsTrue(propGroupCollection["Group2"].PropertyTypes.Contains("property4")); +// Assert.AreEqual(2, propGroupCollection["Group3"].PropertyTypes.Count); +// Assert.IsTrue(propGroupCollection["Group3"].PropertyTypes.Contains("property6")); +// Assert.IsTrue(propGroupCollection["Group3"].PropertyTypes.Contains("property7")); +// Assert.AreEqual(0, propGroupCollection["Group4"].PropertyTypes.Count); - Assert.AreEqual(1, propTypeCollection.Count); - Assert.IsTrue(propTypeCollection.Contains("property5")); - - } - - } -} +// Assert.AreEqual(1, propTypeCollection.Count); +// Assert.IsTrue(propTypeCollection.Contains("property5")); +// } +// } +//} diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 03aae74920..53f150f140 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; -using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -20,7 +17,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Persistence.Repositories { [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class ContentTypeRepositoryTest : TestWithDatabaseBase { public override void SetUp() @@ -32,25 +29,28 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository) { - var cacheHelper = AppCaches.Disabled; - var templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, TestObjects.GetFileSystemsMock()); - var tagRepository = new TagRepository(scopeAccessor, cacheHelper, Logger); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, cacheHelper, Logger, templateRepository); - var languageRepository = new LanguageRepository(scopeAccessor, cacheHelper, Logger); - var repository = new DocumentRepository(scopeAccessor, cacheHelper, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger); + var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); + var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); + var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor) { var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); - var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); + var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); return contentTypeRepository; } private MediaTypeRepository CreateMediaTypeRepository(IScopeAccessor scopeAccessor) { - var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); + var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); + var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); return contentTypeRepository; } @@ -60,7 +60,7 @@ namespace Umbraco.Tests.Persistence.Repositories } // TODO: Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id). - + [Test] public void Maps_Templates_Correctly() { @@ -80,13 +80,13 @@ namespace Umbraco.Tests.Persistence.Repositories { templateRepo.Save(template); } - + var contentType = MockedContentTypes.CreateSimpleContentType(); contentType.AllowedTemplates = new[] { templates[0], templates[1] }; contentType.SetDefaultTemplate(templates[0]); repository.Save(contentType); - + //re-get var result = repository.Get(contentType.Id); @@ -107,16 +107,16 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor) provider); var container1 = new EntityContainer(Constants.ObjectTypes.DocumentType) { Name = "blah1" }; containerRepository.Save(container1); - + var container2 = new EntityContainer(Constants.ObjectTypes.DocumentType) { Name = "blah2", ParentId = container1.Id }; containerRepository.Save(container2); - + var contentType = (IContentType)MockedContentTypes.CreateBasicContentType("asdfasdf"); contentType.ParentId = container2.Id; repository.Save(contentType); - + //create a var contentType2 = (IContentType)new ContentType(contentType, "hello") @@ -125,10 +125,10 @@ namespace Umbraco.Tests.Persistence.Repositories }; contentType.ParentId = contentType.Id; repository.Save(contentType2); - + var result = repository.Move(contentType, container1).ToArray(); - + Assert.AreEqual(2, result.Count()); @@ -152,7 +152,7 @@ namespace Umbraco.Tests.Persistence.Repositories var containerRepository = CreateContainerRepository((IScopeAccessor) provider, Constants.ObjectTypes.DocumentTypeContainer); var container = new EntityContainer(Constants.ObjectTypes.DocumentType) { Name = "blah" }; containerRepository.Save(container); - + Assert.That(container.Id, Is.GreaterThan(0)); var found = containerRepository.Get(container.Id); @@ -176,7 +176,7 @@ namespace Umbraco.Tests.Persistence.Repositories containerRepository.Save(container2); container3 = new EntityContainer(Constants.ObjectTypes.DocumentType) { Name = "container3" }; containerRepository.Save(container3); - + Assert.That(container1.Id, Is.GreaterThan(0)); Assert.That(container2.Id, Is.GreaterThan(0)); Assert.That(container3.Id, Is.GreaterThan(0)); @@ -201,11 +201,11 @@ namespace Umbraco.Tests.Persistence.Repositories var containerRepository = CreateContainerRepository((IScopeAccessor) provider, Constants.ObjectTypes.DocumentTypeContainer); var container = new EntityContainer(Constants.ObjectTypes.DocumentType) { Name = "blah" }; containerRepository.Save(container); - + // Act containerRepository.Delete(container); - + var found = containerRepository.Get(container.Id); Assert.IsNull(found); @@ -222,12 +222,12 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor) provider); var container = new EntityContainer(Constants.ObjectTypes.MediaType) { Name = "blah" }; containerRepository.Save(container); - + var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test", propertyGroupName: "testGroup"); contentType.ParentId = container.Id; repository.Save(contentType); - + Assert.AreEqual(container.Id, contentType.ParentId); } @@ -243,16 +243,16 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateMediaTypeRepository((IScopeAccessor) provider); var container = new EntityContainer(Constants.ObjectTypes.MediaType) { Name = "blah" }; containerRepository.Save(container); - + IMediaType contentType = MockedContentTypes.CreateSimpleMediaType("test", "Test", propertyGroupName: "testGroup"); contentType.ParentId = container.Id; repository.Save(contentType); - + // Act containerRepository.Delete(container); - + var found = containerRepository.Get(container.Id); Assert.IsNull(found); @@ -274,7 +274,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test", propertyGroupName: "testGroup"); repository.Save(contentType); - + var fetched = repository.Get(contentType.Id); @@ -294,7 +294,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreNotEqual(propertyType.Key, Guid.Empty); } - TestHelper.AssertPropertyValuesAreEqual(contentType, fetched, "yyyy-MM-dd HH:mm:ss", ignoreProperties: new [] { "DefaultTemplate", "AllowedTemplates", "UpdateDate" }); + TestHelper.AssertPropertyValuesAreEqual(fetched, contentType, "yyyy-MM-dd HH:mm:ss", ignoreProperties: new [] { "DefaultTemplate", "AllowedTemplates", "UpdateDate" }); } } @@ -323,7 +323,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(4, mapped.PropertyTypes.Count()); repository.Save(mapped); - + Assert.AreEqual(4, mapped.PropertyTypes.Count()); @@ -371,7 +371,7 @@ namespace Umbraco.Tests.Persistence.Repositories DataTypeId = -88 }); repository.Save(contentType); - + var dirty = contentType.IsDirty(); @@ -469,7 +469,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(mapped.PropertyTypes.Any(x => x.Alias == "subtitle")); repository.Save(mapped); - + var dirty = mapped.IsDirty(); @@ -500,11 +500,11 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var contentType = MockedContentTypes.CreateSimpleContentType(); repository.Save(contentType); - + var contentType2 = repository.Get(contentType.Id); repository.Delete(contentType2); - + var exists = repository.Exists(contentType.Id); @@ -528,13 +528,13 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(ctMain); repository.Save(ctChild1); repository.Save(ctChild2); - + // Act var resolvedParent = repository.Get(ctMain.Id); repository.Delete(resolvedParent); - + // Assert Assert.That(repository.Exists(ctMain.Id), Is.False); @@ -546,19 +546,27 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Perform_Query_On_ContentTypeRepository_Sort_By_Name() { + IContentType contentType; + // Arrange var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { var repository = CreateRepository((IScopeAccessor) provider); - var contentType = repository.Get(NodeDto.NodeIdSeed + 1); + contentType = repository.Get(NodeDto.NodeIdSeed + 1); var child1 = MockedContentTypes.CreateSimpleContentType("abc", "abc", contentType, randomizeAliases: true); repository.Save(child1); var child3 = MockedContentTypes.CreateSimpleContentType("zyx", "zyx", contentType, randomizeAliases: true); repository.Save(child3); var child2 = MockedContentTypes.CreateSimpleContentType("a123", "a123", contentType, randomizeAliases: true); repository.Save(child2); - + + scope.Complete(); + } + + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository((IScopeAccessor)provider); // Act var contentTypes = repository.Get(scope.SqlContext.Query().Where(x => x.ParentId == contentType.Id)); @@ -601,7 +609,7 @@ namespace Umbraco.Tests.Persistence.Repositories var contentType = repository.Get(NodeDto.NodeIdSeed + 1); var childContentType = MockedContentTypes.CreateSimpleContentType("blah", "Blah", contentType, randomizeAliases:true); repository.Save(childContentType); - + // Act var result = repository.Get(childContentType.Key); @@ -703,7 +711,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act contentType.PropertyGroups["Meta"].PropertyTypes.Remove("description"); repository.Save(contentType); - + var result = repository.Get(NodeDto.NodeIdSeed + 1); @@ -779,7 +787,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(5)); repository.Save(contentType); - + // Assert var updated = repository.Get(NodeDto.NodeIdSeed + 1); @@ -804,7 +812,7 @@ namespace Umbraco.Tests.Persistence.Repositories var simpleSubpageContentType = MockedContentTypes.CreateSimpleContentType("umbSimpleSubpage", "Simple Subpage"); repository.Save(subpageContentType); repository.Save(simpleSubpageContentType); - + // Act var contentType = repository.Get(NodeDto.NodeIdSeed); @@ -814,7 +822,7 @@ namespace Umbraco.Tests.Persistence.Repositories new ContentTypeSort(new Lazy(() => simpleSubpageContentType.Id), 1, simpleSubpageContentType.Alias) }; repository.Save(contentType); - + //Assert var updated = repository.Get(NodeDto.NodeIdSeed); @@ -838,12 +846,12 @@ namespace Umbraco.Tests.Persistence.Repositories var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.Save(subpage); - + // Act contentType.RemovePropertyType("keywords"); repository.Save(contentType); - + // Assert Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(3)); @@ -865,13 +873,13 @@ namespace Umbraco.Tests.Persistence.Repositories var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.Save(subpage); - + // Act var propertyGroup = contentType.PropertyGroups.First(x => x.Name == "Meta"); propertyGroup.PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "metaAuthor") { Name = "Meta Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); repository.Save(contentType); - + // Assert Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(5)); @@ -893,18 +901,18 @@ namespace Umbraco.Tests.Persistence.Repositories var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.Save(subpage); - + var propertyGroup = contentType.PropertyGroups.First(x => x.Name == "Meta"); propertyGroup.PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "metaAuthor") { Name = "Meta Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); repository.Save(contentType); - + // Act var content = contentRepository.Get(subpage.Id); content.SetValue("metaAuthor", "John Doe"); contentRepository.Save(content); - + //Assert var updated = contentRepository.Get(subpage.Id); @@ -927,7 +935,7 @@ namespace Umbraco.Tests.Persistence.Repositories var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.Save(subpage); - + //Remove PropertyType contentType.RemovePropertyType("keywords"); @@ -935,13 +943,13 @@ namespace Umbraco.Tests.Persistence.Repositories var propertyGroup = contentType.PropertyGroups.First(x => x.Name == "Meta"); propertyGroup.PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "metaAuthor") { Name = "Meta Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); repository.Save(contentType); - + // Act var content = contentRepository.Get(subpage.Id); content.SetValue("metaAuthor", "John Doe"); contentRepository.Save(content); - + //Assert var updated = contentRepository.Get(subpage.Id); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 2c4f3f1908..fd797662c0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -66,7 +66,8 @@ namespace Umbraco.Tests.Persistence.Repositories templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; @@ -141,8 +142,8 @@ namespace Umbraco.Tests.Persistence.Repositories // publish = new edit version content1.SetValue("title", "title"); - ((Content)content1).PublishCulture(); - ((Content)content1).PublishedState = PublishedState.Publishing; + content1.PublishCulture(CultureImpact.Invariant); + content1.PublishedState = PublishedState.Publishing; repository.Save(content1); versions.Add(content1.VersionId); // NEW VERSION @@ -203,8 +204,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(false, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // publish = version - ((Content)content1).PublishCulture(); - ((Content)content1).PublishedState = PublishedState.Publishing; + content1.PublishCulture(CultureImpact.Invariant); + content1.PublishedState = PublishedState.Publishing; repository.Save(content1); versions.Add(content1.VersionId); // NEW VERSION @@ -239,8 +240,8 @@ namespace Umbraco.Tests.Persistence.Repositories // publish = new version content1.Name = "name-4"; content1.SetValue("title", "title-4"); - ((Content)content1).PublishCulture(); - ((Content)content1).PublishedState = PublishedState.Publishing; + content1.PublishCulture(CultureImpact.Invariant); + content1.PublishedState = PublishedState.Publishing; repository.Save(content1); versions.Add(content1.VersionId); // NEW VERSION @@ -654,7 +655,7 @@ namespace Umbraco.Tests.Persistence.Repositories // publish them all foreach (var content in result) { - content.PublishCulture(); + content.PublishCulture(CultureImpact.Invariant); repository.Save(content); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index d0da875332..f00b2fd046 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -22,7 +22,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); + contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository); languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 1c1b5e60f4..1d9cf6d022 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -4,6 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; @@ -35,7 +36,9 @@ namespace Umbraco.Tests.Persistence.Repositories appCaches = appCaches ?? AppCaches; var scopeAccessor = (IScopeAccessor) provider; - mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger); + var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock()); + var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches); + mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index 49bb93f2a7..f302d1d992 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -6,7 +6,6 @@ using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -21,7 +20,10 @@ namespace Umbraco.Tests.Persistence.Repositories { private MediaTypeRepository CreateRepository(IScopeProvider provider) { - return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var cacheHelper = AppCaches.Disabled; + var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock()); + var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches); + return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); } private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index c46a090685..a5f7f08f22 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -29,7 +29,9 @@ namespace Umbraco.Tests.Persistence.Repositories private MemberRepository CreateRepository(IScopeProvider provider, out MemberTypeRepository memberTypeRepository, out MemberGroupRepository memberGroupRepository) { var accessor = (IScopeAccessor) provider; - memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger); + var templateRepository = Mock.Of(); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); + memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger); var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger); var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); @@ -196,17 +198,17 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType = MockedContentTypes.CreateSimpleMemberType(); memberTypeRepository.Save(memberType); - + var member = MockedMember.CreateSimpleMember(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty"); repository.Save(member); - + sut = repository.Get(member.Id); //when the password is null it will not overwrite what is already there. sut.RawPasswordValue = null; repository.Save(sut); - + sut = repository.Get(member.Id); Assert.That(sut.RawPasswordValue, Is.EqualTo("123")); @@ -226,17 +228,17 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType = MockedContentTypes.CreateSimpleMemberType(); memberTypeRepository.Save(memberType); - + var member = MockedMember.CreateSimpleMember(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty"); repository.Save(member); - + sut = repository.Get(member.Id); sut.Username = "This is new"; sut.Email = "thisisnew@hello.com"; repository.Save(sut); - + sut = repository.Get(member.Id); Assert.That(sut.Email, Is.EqualTo("thisisnew@hello.com")); @@ -278,7 +280,7 @@ namespace Umbraco.Tests.Persistence.Repositories { memberType = MockedContentTypes.CreateSimpleMemberType(); memberTypeRepository.Save(memberType); - + } var member = MockedMember.CreateSimpleMember(memberType, name ?? "Johnny Hefty", email ?? "johnny@example.com", password ?? "123", username ?? "hefty", key); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 0c2314fd47..2b3ab50a22 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -22,7 +22,9 @@ namespace Umbraco.Tests.Persistence.Repositories { private MemberTypeRepository CreateRepository(IScopeProvider provider) { - return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); + var templateRepository = Mock.Of(); + var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches); + return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository); } [Test] @@ -35,7 +37,6 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType = (IMemberType) MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType); - var sut = repository.Get(memberType.Id); @@ -88,7 +89,7 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType = MockedContentTypes.CreateSimpleMemberType(); memberType.Alias = null; - + Assert.Throws(() => repository.Save(memberType)); } @@ -104,13 +105,13 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType1 = MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType1); - + var memberType2 = MockedContentTypes.CreateSimpleMemberType(); memberType2.Name = "AnotherType"; memberType2.Alias = "anotherType"; repository.Save(memberType2); - + var result = repository.GetMany(); @@ -129,13 +130,13 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType1 = MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType1); - + var memberType2 = MockedContentTypes.CreateSimpleMemberType(); memberType2.Name = "AnotherType"; memberType2.Alias = "anotherType"; repository.Save(memberType2); - + var result = ((IReadRepository)repository).GetMany(memberType1.Key, memberType2.Key); @@ -154,13 +155,13 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType1 = MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType1); - + var memberType2 = MockedContentTypes.CreateSimpleMemberType(); memberType2.Name = "AnotherType"; memberType2.Alias = "anotherType"; repository.Save(memberType2); - + var result = repository.Get(memberType1.Key); @@ -183,14 +184,14 @@ namespace Umbraco.Tests.Persistence.Repositories var memberType1 = MockedContentTypes.CreateSimpleMemberType(); memberType1.PropertyTypeCollection.Clear(); repository.Save(memberType1); - + var memberType2 = MockedContentTypes.CreateSimpleMemberType(); memberType2.PropertyTypeCollection.Clear(); memberType2.Name = "AnotherType"; memberType2.Alias = "anotherType"; repository.Save(memberType2); - + var result = repository.GetMany(); @@ -210,7 +211,7 @@ namespace Umbraco.Tests.Persistence.Repositories IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType); - + memberType = repository.Get(memberType.Id); Assert.That(memberType, Is.Not.Null); } @@ -226,7 +227,7 @@ namespace Umbraco.Tests.Persistence.Repositories IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType); - + memberType = repository.Get(memberType.Key); Assert.That(memberType, Is.Not.Null); } @@ -242,7 +243,7 @@ namespace Umbraco.Tests.Persistence.Repositories IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType); - + memberType = repository.Get(memberType.Id); @@ -265,7 +266,7 @@ namespace Umbraco.Tests.Persistence.Repositories IMemberType memberType2 = MockedContentTypes.CreateSimpleMemberType("test2"); repository.Save(memberType1); repository.Save(memberType2); - + var m1Ids = memberType1.PropertyTypes.Select(x => x.Id).ToArray(); var m2Ids = memberType2.PropertyTypes.Select(x => x.Id).ToArray(); @@ -286,11 +287,11 @@ namespace Umbraco.Tests.Persistence.Repositories // Act IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); repository.Save(memberType); - + var contentType2 = repository.Get(memberType.Id); repository.Delete(contentType2); - + var exists = repository.Exists(memberType.Id); diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 684c8f9a22..803eff25af 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -307,7 +307,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index ddbf3bfc9d..b6cc4dc50d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -955,7 +955,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; @@ -964,8 +965,10 @@ namespace Umbraco.Tests.Persistence.Repositories private MediaRepository CreateMediaRepository(IScopeProvider provider, out MediaTypeRepository mediaTypeRepository) { var accessor = (IScopeAccessor) provider; + var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); - mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 8f0150277d..13cbd463fb 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -238,7 +238,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = CreateRepository(ScopeProvider); var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); - var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches); + var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 1d50fcac9e..b550091591 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -3,8 +3,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Mappers; @@ -26,7 +24,9 @@ namespace Umbraco.Tests.Persistence.Repositories private MediaRepository CreateMediaRepository(IScopeProvider provider, out IMediaTypeRepository mediaTypeRepository) { var accessor = (IScopeAccessor) provider; - mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of()); + var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository); var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of()); var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of()); return repository; @@ -43,7 +43,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index dcbf5919eb..2afbdaca8f 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -316,7 +316,7 @@ namespace Umbraco.Tests.Persistence helper.CreateTable(); helper.CreateTable(); - helper.CreateTable(); + helper.CreateTable(); scope.Complete(); } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 3142e1038e..ad2b0220bb 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; +using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; @@ -181,7 +182,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, new SiteDomainHelper(), Mock.Of(), - Mock.Of()); + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index ab576171f4..de641a99a2 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -10,6 +10,7 @@ using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Core.Composing; using Moq; +using Newtonsoft.Json; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -32,6 +33,8 @@ namespace Umbraco.Tests.PublishedContent protected override void Compose() { base.Compose(); + _publishedSnapshotAccessorMock = new Mock(); + Composition.RegisterUnique(_publishedSnapshotAccessorMock.Object); Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); Composition.RegisterUnique(); @@ -87,6 +90,7 @@ namespace Umbraco.Tests.PublishedContent } private readonly Guid _node1173Guid = Guid.NewGuid(); + private Mock _publishedSnapshotAccessorMock; protected override string GetXmlContent(int templateId) { @@ -792,6 +796,91 @@ namespace Umbraco.Tests.PublishedContent Assert.IsTrue(customDoc3.IsDescendantOrSelf(customDoc3)); } + [Test] + public void SiblingsAndSelf() + { + // Structure: + // - Root : 1046 (no parent) + // -- Level1.1: 1173 (parent 1046) + // --- Level1.1.1: 1174 (parent 1173) + // --- Level1.1.2: 117 (parent 1173) + // --- Level1.1.3: 1177 (parent 1173) + // --- Level1.1.4: 1178 (parent 1173) + // --- Level1.1.5: 1176 (parent 1173) + // -- Level1.2: 1175 (parent 1046) + // -- Level1.3: 4444 (parent 1046) + var root = GetNode(1046); + var level1_1 = GetNode(1173); + var level1_1_1 = GetNode(1174); + var level1_1_2 = GetNode(117); + var level1_1_3 = GetNode(1177); + var level1_1_4 = GetNode(1178); + var level1_1_5 = GetNode(1176); + var level1_2 = GetNode(1175); + var level1_3 = GetNode(4444); + + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + + CollectionAssertAreEqual(new []{root}, root.SiblingsAndSelf()); + + CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_1.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_2.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_3.SiblingsAndSelf()); + + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_3.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_4.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_5.SiblingsAndSelf()); + + } + + [Test] + public void Siblings() + { + // Structure: + // - Root : 1046 (no parent) + // -- Level1.1: 1173 (parent 1046) + // --- Level1.1.1: 1174 (parent 1173) + // --- Level1.1.2: 117 (parent 1173) + // --- Level1.1.3: 1177 (parent 1173) + // --- Level1.1.4: 1178 (parent 1173) + // --- Level1.1.5: 1176 (parent 1173) + // -- Level1.2: 1175 (parent 1046) + // -- Level1.3: 4444 (parent 1046) + var root = GetNode(1046); + var level1_1 = GetNode(1173); + var level1_1_1 = GetNode(1174); + var level1_1_2 = GetNode(117); + var level1_1_3 = GetNode(1177); + var level1_1_4 = GetNode(1178); + var level1_1_5 = GetNode(1176); + var level1_2 = GetNode(1175); + var level1_3 = GetNode(4444); + + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + + CollectionAssertAreEqual(new IPublishedContent[0], root.Siblings()); + + CollectionAssertAreEqual( new []{level1_2, level1_3}, level1_1.Siblings()); + CollectionAssertAreEqual( new []{level1_1, level1_3}, level1_2.Siblings()); + CollectionAssertAreEqual( new []{level1_1, level1_2}, level1_3.Siblings()); + + CollectionAssertAreEqual( new []{ level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_4, level1_1_5}, level1_1_3.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_5}, level1_1_4.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4}, level1_1_5.Siblings()); + + } + + private void CollectionAssertAreEqual(IEnumerable expected, IEnumerable actual) + where T: IPublishedContent + { + var e = expected.Select(x => x.Id); + var a = actual.Select(x => x.Id); + CollectionAssert.AreEquivalent(e, a, $"\nExpected:\n{string.Join(", ", e)}\n\nActual:\n{string.Join(", ", a)}"); + } [Test] public void FragmentProperty() diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 93c6100a51..f1f38504f1 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing { base.SetUp(); - WebRuntimeComponent.CreateRoutes( + WebFinalComponent.CreateRoutes( new TestUmbracoContextAccessor(), TestObjects.GetGlobalSettings(), new SurfaceControllerTypeCollection(Enumerable.Empty()), diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 915199654f..0f99b6b884 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Runtimes var composerTypes = typeLoader.GetTypes() // all of them .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components - .Where(x => x != typeof(WebRuntimeComposer)); // exclude web runtime + .Where(x => x != typeof(WebInitialComposer)); // exclude web runtime var composers = new Composers(composition, composerTypes, profilingLogger); composers.Compose(); diff --git a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs index 27e5328f6f..b0d2788b58 100644 --- a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs +++ b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Runtimes new PluginViewEngine() }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(engines[0], Is.InstanceOf()); @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Runtimes new PluginViewEngine() }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); @@ -50,7 +50,7 @@ namespace Umbraco.Tests.Runtimes profiledEngine }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines[0], Is.SameAs(profiledEngine)); } @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Runtimes [Test] public void WrapViewEngines_CollectionIsNull_DoesNotThrow() { - Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null)); + Assert.DoesNotThrow(() => WebInitialComponent.WrapViewEngines(null)); } } } diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 9b1300ee23..1dcc928141 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -99,7 +100,8 @@ namespace Umbraco.Tests.Scoping new DatabaseDataSource(), Factory.GetInstance(), new SiteDomainHelper(), Factory.GetInstance(), - Mock.Of()); + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index cb7a984ff3..ed5e6073ac 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -165,7 +165,8 @@ namespace Umbraco.Tests.Services { var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); @@ -198,7 +199,8 @@ namespace Umbraco.Tests.Services { var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); @@ -229,7 +231,8 @@ namespace Umbraco.Tests.Services { var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); @@ -263,7 +266,8 @@ namespace Umbraco.Tests.Services { var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, tRepository); + var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); diff --git a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs index 5e97bea2c1..6b4f2942f7 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs @@ -517,7 +517,7 @@ namespace Umbraco.Tests.Services allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content1.PublishCulture(); + content1.PublishCulture(CultureImpact.Invariant); contentService.SaveAndPublish(content1); Assert.IsTrue(content1.Published); @@ -601,7 +601,7 @@ namespace Umbraco.Tests.Services var allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content1.PublishCulture(); + content1.PublishCulture(CultureImpact.Invariant); contentService.SaveAndPublish(content1); tags = tagService.GetTagsForEntity(content2.Id); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 04cdc2aab7..222f40aeed 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -732,8 +732,8 @@ namespace Umbraco.Tests.Services IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-fr", langFr.IsoCode); content.SetCultureName("content-en", langUk.IsoCode); - content.PublishCulture(langFr.IsoCode); - content.PublishCulture(langUk.IsoCode); + content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault)); + content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault)); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); @@ -757,7 +757,7 @@ namespace Umbraco.Tests.Services content = ServiceContext.ContentService.GetById(content.Id); Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - + } @@ -784,7 +784,7 @@ namespace Umbraco.Tests.Services IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-en", langGB.IsoCode); content.SetCultureName("content-fr", langFr.IsoCode); - + Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content, new []{ langGB.IsoCode , langFr.IsoCode }).Success); //re-get @@ -963,6 +963,18 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Home", e.Name); } + [Test] + public void Can_Not_Publish_Invalid_Cultures() + { + var contentService = ServiceContext.ContentService; + var content = Mock.Of(c => c.ContentType == Mock.Of(s => s.Variations == ContentVariation.Culture)); + + Assert.Throws(() => contentService.SaveAndPublish(content, new[] {"*"})); + Assert.Throws(() => contentService.SaveAndPublish(content, new string[] { null })); + Assert.Throws(() => contentService.SaveAndPublish(content, new[] { "*", null })); + Assert.Throws(() => contentService.SaveAndPublish(content, new[] { "en-US", "*", "es-ES" })); + } + [Test] public void Can_Publish_Only_Valid_Content() { @@ -973,10 +985,7 @@ namespace Umbraco.Tests.Services const int parentId = NodeDto.NodeIdSeed + 2; var contentService = ServiceContext.ContentService; - var content = MockedContent.CreateSimpleContent(contentType, "Invalid Content", parentId); - content.SetValue("author", string.Empty); - contentService.Save(content); - + var parent = contentService.GetById(parentId); var parentPublished = contentService.SaveAndPublish(parent); @@ -986,9 +995,13 @@ namespace Umbraco.Tests.Services Assert.IsTrue(parentPublished.Success); Assert.IsTrue(parent.Published); + var content = MockedContent.CreateSimpleContent(contentType, "Invalid Content", parentId); + content.SetValue("author", string.Empty); + Assert.IsFalse(content.HasIdentity); + // content cannot publish values because they are invalid var propertyValidationService = new PropertyValidationService(Factory.GetInstance(), ServiceContext.DataTypeService); - var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties); + var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties, CultureImpact.Invariant); Assert.IsFalse(isValid); Assert.IsNotEmpty(invalidProperties); @@ -998,7 +1011,12 @@ namespace Umbraco.Tests.Services Assert.IsFalse(contentPublished.Success); Assert.AreEqual(PublishResultType.FailedPublishContentInvalid, contentPublished.Result); Assert.IsFalse(content.Published); + + //Ensure it saved though + Assert.Greater(content.Id, 0); + Assert.IsTrue(content.HasIdentity); } + [Test] public void Can_Publish_And_Unpublish_Cultures_In_Single_Operation() @@ -1018,7 +1036,7 @@ namespace Umbraco.Tests.Services content.SetCultureName("name-fr", langFr.IsoCode); content.SetCultureName("name-da", langDa.IsoCode); - content.PublishCulture(langFr.IsoCode); + content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault)); var result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content); Assert.IsTrue(result.Success); content = ServiceContext.ContentService.GetById(content.Id); @@ -1026,7 +1044,7 @@ namespace Umbraco.Tests.Services Assert.IsFalse(content.IsCulturePublished(langDa.IsoCode)); content.UnpublishCulture(langFr.IsoCode); - content.PublishCulture(langDa.IsoCode); + content.PublishCulture(CultureImpact.Explicit(langDa.IsoCode, langDa.IsDefault)); result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content); Assert.IsTrue(result.Success); @@ -1579,7 +1597,7 @@ namespace Umbraco.Tests.Services var contentType = MockedContentTypes.CreateAllTypesContentType("test", "test"); ServiceContext.ContentTypeService.Save(contentType, Constants.Security.SuperUserId); - + object obj = new { @@ -2989,7 +3007,8 @@ namespace Umbraco.Tests.Services var accessor = (IScopeAccessor) provider; var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, templateRepository); + var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 341371ca02..a0b5f01a1f 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -1654,17 +1654,31 @@ namespace Umbraco.Tests.Services // create 'page' content type with a 'Content_' group var page = MockedContentTypes.CreateSimpleContentType("page", "Page", null, false, "Content_"); - Assert.IsTrue(page.PropertyGroups.Contains("Content_")); + Assert.AreEqual(1, page.PropertyGroups.Count); + Assert.AreEqual("Content_", page.PropertyGroups.First().Name); Assert.AreEqual(3, page.PropertyTypes.Count()); + Assert.AreEqual("Title", page.PropertyTypes.First().Name); + Assert.AreEqual("Body Text", page.PropertyTypes.Skip(1).First().Name); + Assert.AreEqual("Author", page.PropertyTypes.Skip(2).First().Name); service.Save(page); // create 'contentPage' content type as a child of 'page' var contentPage = MockedContentTypes.CreateSimpleContentType("contentPage", "Content Page", page, true); + Assert.AreEqual(1, page.PropertyGroups.Count); + Assert.AreEqual("Content_", page.PropertyGroups.First().Name); Assert.AreEqual(3, contentPage.PropertyTypes.Count()); + Assert.AreEqual("Title", contentPage.PropertyTypes.First().Name); + Assert.AreEqual("Body Text", contentPage.PropertyTypes.Skip(1).First().Name); + Assert.AreEqual("Author", contentPage.PropertyTypes.Skip(2).First().Name); service.Save(contentPage); // add 'Content' group to 'meta' content type var meta = MockedContentTypes.CreateMetaContentType(); + Assert.AreEqual(1, meta.PropertyGroups.Count); + Assert.AreEqual("Meta", meta.PropertyGroups.First().Name); + Assert.AreEqual(2, meta.PropertyTypes.Count()); + Assert.AreEqual("Meta Keywords", meta.PropertyTypes.First().Name); + Assert.AreEqual("Meta Description", meta.PropertyTypes.Skip(1).First().Name); meta.AddPropertyGroup("Content"); Assert.AreEqual(2, meta.PropertyTypes.Count()); service.Save(meta); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index b5bf9e61f8..c70b96a175 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -72,7 +73,8 @@ namespace Umbraco.Tests.Services new DatabaseDataSource(), Factory.GetInstance(), new SiteDomainHelper(), Factory.GetInstance(), - Mock.Of()); + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); } public class LocalServerMessenger : ServerMessengerBase diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 449e933c24..9cf38e1789 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -216,7 +216,7 @@ namespace Umbraco.Tests.Services var result = new List(); ServiceContext.ContentTypeService.Save(contentType1); IContent lastParent = MockedContent.CreateSimpleContent(contentType1); - lastParent.PublishCulture(); + lastParent.PublishCulture(CultureImpact.Invariant); ServiceContext.ContentService.SaveAndPublish(lastParent); result.Add(lastParent); //create 20 deep @@ -230,7 +230,7 @@ namespace Umbraco.Tests.Services //only publish evens if (j % 2 == 0) { - content.PublishCulture(); + content.PublishCulture(CultureImpact.Invariant); ServiceContext.ContentService.SaveAndPublish(content); } else diff --git a/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs new file mode 100644 index 0000000000..e1e19918ce --- /dev/null +++ b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs @@ -0,0 +1,177 @@ +using System.Threading; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; +using Umbraco.Core.Services; +using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class PropertyValidationServiceTests : UmbracoTestBase + { + private void MockObjects(out PropertyValidationService validationService, out IDataType dt) + { + var textService = new Mock(); + textService.Setup(x => x.Localize(It.IsAny(), Thread.CurrentThread.CurrentCulture, null)).Returns("Localized text"); + + var dataTypeService = new Mock(); + var dataType = Mock.Of( + x => x.Configuration == (object)string.Empty //irrelevant but needs a value + && x.DatabaseType == ValueStorageType.Nvarchar + && x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox); + dataTypeService.Setup(x => x.GetDataType(It.IsAny())).Returns(() => dataType); + dt = dataType; + + //new data editor that returns a TextOnlyValueEditor which will do the validation for the properties + var dataEditor = Mock.Of( + x => x.Type == EditorType.PropertyValue + && x.Alias == Constants.PropertyEditors.Aliases.TextBox); + Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny())) + .Returns(new CustomTextOnlyValueEditor(new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox, "Test Textbox", "textbox"), textService.Object)); + + var propEditors = new PropertyEditorCollection(new DataEditorCollection(new[] { dataEditor })); + + validationService = new PropertyValidationService(propEditors, dataTypeService.Object); + } + + [Test] + public void Validate_Invariant_Properties_On_Variant_Default_Culture() + { + MockObjects(out var validationService, out var dataType); + + var p1 = new Property(new PropertyType(dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture}); + p1.SetValue("Hello", "en-US"); + var p2 = new Property(new PropertyType(dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); + p2.SetValue("Hello", null); + var p3 = new Property(new PropertyType(dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); + p3.SetValue(null, "en-US"); //invalid + var p4 = new Property(new PropertyType(dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); + p4.SetValue(null, null); //invalid + + var content = Mock.Of( + x => x.Published == true //set to published, the default culture will validate invariant anyways + && x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); + + var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", true)); + + Assert.IsFalse(result); + Assert.AreEqual(2, invalid.Length); + } + + [Test] + public void Validate_Invariant_Properties_On_Variant_Non_Default_Culture() + { + MockObjects(out var validationService, out var dataType); + + var p1 = new Property(new PropertyType(dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); + p1.SetValue("Hello", "en-US"); + var p2 = new Property(new PropertyType(dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); + p2.SetValue("Hello", null); + var p3 = new Property(new PropertyType(dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); + p3.SetValue(null, "en-US"); //invalid + var p4 = new Property(new PropertyType(dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); + p4.SetValue(null, null); //invalid + + var content = Mock.Of( + x => x.Published == false //set to not published, the non default culture will need to validate invariant too + && x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); + + var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", false)); + + Assert.IsFalse(result); + Assert.AreEqual(2, invalid.Length); + } + + [Test] + public void Validate_Variant_Properties_On_Variant() + { + MockObjects(out var validationService, out var dataType); + + var p1 = new Property(new PropertyType(dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); + p1.SetValue(null, "en-US"); //invalid + var p2 = new Property(new PropertyType(dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); + p2.SetValue(null, null); //invalid + var p3 = new Property(new PropertyType(dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); + p3.SetValue(null, "en-US"); //ignored because the impact isn't the default lang + the content is published + var p4 = new Property(new PropertyType(dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); + p4.SetValue(null, null); //ignored because the impact isn't the default lang + the content is published + + var content = Mock.Of( + x => x.Published == true //set to published + && x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); + + var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", false)); + + Assert.IsFalse(result); + Assert.AreEqual(2, invalid.Length); + } + + [Test] + public void Validate_Invariant_Properties_On_Invariant() + { + MockObjects(out var validationService, out var dataType); + + var p1 = new Property(new PropertyType(dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); + p1.SetValue(null, "en-US"); //ignored since this is variant + var p2 = new Property(new PropertyType(dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); + p2.SetValue(null, null); //invalid + var p3 = new Property(new PropertyType(dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); + p3.SetValue("Hello", "en-US"); //ignored since this is variant + var p4 = new Property(new PropertyType(dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); + p4.SetValue(null, null); //invalid + + var content = Mock.Of( + x => x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); + + var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Invariant); + + Assert.IsFalse(result); + Assert.AreEqual(2, invalid.Length); + } + + [Test] + public void Validate_Properties_On_All() + { + MockObjects(out var validationService, out var dataType); + + var p1 = new Property(new PropertyType(dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); + p1.SetValue(null, "en-US"); //invalid + var p2 = new Property(new PropertyType(dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); + p2.SetValue(null, null); //invalid + var p3 = new Property(new PropertyType(dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); + p3.SetValue(null, "en-US"); //invalid + var p4 = new Property(new PropertyType(dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); + p4.SetValue(null, null); //invalid + + var content = Mock.Of( + x => x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); + + var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.All); + + Assert.IsFalse(result); + Assert.AreEqual(4, invalid.Length); + } + + + //used so we can inject a mock - we should fix the base class DataValueEditor to be able to have the ILocalizedTextField passed + // in to create the Requried and Regex validators so we aren't using singletons + private class CustomTextOnlyValueEditor : TextOnlyValueEditor + { + private readonly ILocalizedTextService _textService; + + public CustomTextOnlyValueEditor(DataEditorAttribute attribute, ILocalizedTextService textService) : base(attribute) + { + _textService = textService; + } + + public override IValueRequiredValidator RequiredValidator => new RequiredValidator(_textService); + + public override IValueFormatValidator FormatValidator => new RegexValidator(_textService, null); + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index ca32e71e5b..f62effcb62 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,4 +1,5 @@ -using Moq; +using System; +using Moq; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -33,8 +34,6 @@ namespace Umbraco.Tests.TestHelpers { Current.Reset(); - var sqlSyntax = new SqlCeSyntaxProvider(); - var container = RegisterFactory.Create(); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); @@ -51,15 +50,17 @@ namespace Umbraco.Tests.TestHelpers composition.RegisterUnique(typeLoader); composition.WithCollectionBuilder() - .Add(() => composition.TypeLoader.GetAssignedMapperTypes()); + .AddCoreMappers(); + + composition.RegisterUnique(_ => SqlContext); var factory = Current.Factory = composition.CreateFactory(); - Mappers = factory.GetInstance(); - var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); - SqlContext = new SqlContext(sqlSyntax, DatabaseType.SQLCe, pocoDataFactory, Mappers); + var sqlSyntax = new SqlCeSyntaxProvider(); + SqlContext = new SqlContext(sqlSyntax, DatabaseType.SQLCe, pocoDataFactory, new Lazy(() => factory.GetInstance())); + Mappers = factory.GetInstance(); SetUp(); } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 0f84319976..75e9cd60cb 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -14,6 +14,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; @@ -37,10 +38,14 @@ namespace Umbraco.Tests.TestHelpers /// This is just a void factory that has no actual database. public IUmbracoDatabaseFactory GetDatabaseFactoryMock(bool configured = true, bool canConnect = true) { + var sqlSyntax = new SqlCeSyntaxProvider(); + var sqlContext = Mock.Of(); + Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(sqlSyntax); + var databaseFactoryMock = new Mock(); databaseFactoryMock.Setup(x => x.Configured).Returns(configured); databaseFactoryMock.Setup(x => x.CanConnect).Returns(canConnect); - databaseFactoryMock.Setup(x => x.SqlContext).Returns(Mock.Of()); + databaseFactoryMock.Setup(x => x.SqlContext).Returns(sqlContext); // can create a database - but don't try to use it! if (configured && canConnect) @@ -116,7 +121,7 @@ namespace Umbraco.Tests.TestHelpers var umbracoSettings = GetUmbracoSettings(); var globalSettings = GetGlobalSettings(); - var urlProviders = Enumerable.Empty(); + var urlProviders = new UrlProviderCollection(Enumerable.Empty()); if (accessor == null) accessor = new TestUmbracoContextAccessor(); diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 643deab304..7f3c855593 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -85,9 +85,8 @@ namespace Umbraco.Tests.TestHelpers if (Options.Database == UmbracoTestOptions.Database.None) return TestObjects.GetDatabaseFactoryMock(); - var logger = f.GetInstance(); - var mappers = f.GetInstance(); - var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), logger, new Lazy(() => mappers)); + var lazyMappers = new Lazy(f.GetInstance); + var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), f.GetInstance(), lazyMappers); factory.ResetForTests(); return factory; }); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs b/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs index 4013d93cd3..b33a0ad69a 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs @@ -12,17 +12,17 @@ namespace Umbraco.Tests.Testing /// /// Default is false. /// This is for tests that inherited from TestWithApplicationBase. - /// Implies AutoMapper = true (, ResetPluginManager = false). + /// Implies Mapper = true (, ResetPluginManager = false). /// public bool WithApplication { get => _withApplication.ValueOrDefault(false); set => _withApplication.Set(value); } private readonly Settable _withApplication = new Settable(); /// - /// Gets or sets a value indicating whether to compose and initialize AutoMapper. + /// Gets or sets a value indicating whether to compose and initialize the mapper. /// /// Default is false unless WithApplication is true, in which case default is true. - public bool AutoMapper { get => _autoMapper.ValueOrDefault(WithApplication); set => _autoMapper.Set(value); } - private readonly Settable _autoMapper = new Settable(); + public bool Mapper { get => _mapper.ValueOrDefault(WithApplication); set => _mapper.Set(value); } + private readonly Settable _mapper = new Settable(); // FIXME: to be completed /// @@ -59,7 +59,7 @@ namespace Umbraco.Tests.Testing base.Merge(other); - _autoMapper.Set(attr._autoMapper); + _mapper.Set(attr._mapper); _publishedRepositoryEvents.Set(attr._publishedRepositoryEvents); _logger.Set(attr._logger); _database.Set(attr._database); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index fedc94d45b..2d2cbaa3fd 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Reflection; -using AutoMapper; using Examine; using Moq; using NUnit.Framework; @@ -37,6 +36,7 @@ using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Trees; using Umbraco.Core.Composing.CompositionExtensions; +using Umbraco.Core.Mapping; using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Sections; using Current = Umbraco.Core.Composing.Current; @@ -108,6 +108,8 @@ namespace Umbraco.Tests.Testing protected IMapperCollection Mappers => Factory.GetInstance(); + protected UmbracoMapper Mapper => Factory.GetInstance(); + #endregion #region Setup @@ -148,7 +150,7 @@ namespace Umbraco.Tests.Testing protected virtual void Compose() { - ComposeAutoMapper(Options.AutoMapper); + ComposeMapper(Options.Mapper); ComposeDatabase(Options.Database); ComposeApplication(Options.WithApplication); @@ -165,7 +167,6 @@ namespace Umbraco.Tests.Testing protected virtual void Initialize() { - InitializeAutoMapper(Options.AutoMapper); InitializeApplication(Options.WithApplication); } @@ -249,7 +250,7 @@ namespace Umbraco.Tests.Testing Composition.WithCollectionBuilder(); } - protected virtual void ComposeAutoMapper(bool configure) + protected virtual void ComposeMapper(bool configure) { if (configure == false) return; @@ -342,7 +343,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(f => new UmbracoDatabaseFactory( Constants.System.UmbracoConnectionName, Logger, - new Lazy(Mock.Of))); + new Lazy(f.GetInstance))); Composition.RegisterUnique(f => f.TryGetInstance().SqlContext); Composition.WithCollectionBuilder(); // empty @@ -371,18 +372,6 @@ namespace Umbraco.Tests.Testing #region Initialize - protected virtual void InitializeAutoMapper(bool configure) - { - if (configure == false) return; - - Mapper.Initialize(configuration => - { - var profiles = Factory.GetAllInstances(); - foreach (var profile in profiles) - configuration.AddProfile(profile); - }); - } - protected virtual void InitializeApplication(bool withApplication) { if (withApplication == false) return; @@ -430,8 +419,6 @@ namespace Umbraco.Tests.Testing UriUtility.ResetAppDomainAppVirtualPath(); SettingsForTests.Reset(); // FIXME: should it be optional? - Mapper.Reset(); - // clear static events DocumentRepository.ClearScopeEvents(); MediaRepository.ClearScopeEvents(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 20cea53b09..6188f577bb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -78,7 +78,6 @@ - @@ -118,6 +117,7 @@ + @@ -130,11 +130,14 @@ + + + @@ -151,6 +154,7 @@ + @@ -162,7 +166,6 @@ - @@ -246,6 +249,7 @@ + @@ -270,7 +274,6 @@ - diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index c48aa2717c..a4c3078b8f 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; @@ -123,7 +125,7 @@ namespace Umbraco.Tests.Web.Controllers var mappers = new MapperCollection(new [] { - new UserMapper() + new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) }); Mock.Get(Current.SqlContext) diff --git a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs b/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs new file mode 100644 index 0000000000..3ce43b5fc2 --- /dev/null +++ b/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs @@ -0,0 +1,81 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web.Http.ModelBinding; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Services; +using Umbraco.Web; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class ModelStateExtensionsTests + { + + [Test] + public void Get_Cultures_With_Errors() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property + ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); //variant property + + var result = ms.GetCulturesWithErrors(localizationService.Object, "en-US"); + + //even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property + Assert.AreEqual(1, result.Count); + Assert.AreEqual("en-US", result[0]); + + ms = new ModelStateDictionary(); + ms.AddCultureValidationError("en-US", "generic culture error"); + + result = ms.GetCulturesWithErrors(localizationService.Object, "en-US"); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("en-US", result[0]); + } + + [Test] + public void Get_Cultures_With_Property_Errors() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property + ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); //variant property + + var result = ms.GetCulturesWithPropertyErrors(localizationService.Object, "en-US"); + + //even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property + Assert.AreEqual(1, result.Count); + Assert.AreEqual("en-US", result[0]); + } + + [Test] + public void Add_Invariant_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property + + Assert.AreEqual("_Properties.headerImage.invariant", ms.Keys.First()); + } + + [Test] + public void Add_Variant_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //invariant property + + Assert.AreEqual("_Properties.headerImage.en-US", ms.Keys.First()); + } + } +} diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index b41aca21c3..0b76de9879 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -71,7 +71,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -100,7 +100,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -158,7 +158,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 846ee4b7d8..7de2dd1aad 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -103,7 +103,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), Mock.Of(section => section.WebRouting == webRoutingSettings), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 8a4e3e515b..5e9208faf9 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -104,7 +104,7 @@ namespace Umbraco.Tests.Web new TestDefaultCultureAccessor(), Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), globalSettings, - new[] { testUrlProvider.Object }, + new UrlProviderCollection(new[] { testUrlProvider.Object }), Mock.Of()); using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) diff --git a/src/Umbraco.Web.UI.Client/setupgulp.bat b/src/Umbraco.Web.UI.Client/setupgulp.bat deleted file mode 100644 index 6a53e11b71..0000000000 --- a/src/Umbraco.Web.UI.Client/setupgulp.bat +++ /dev/null @@ -1,22 +0,0 @@ -@ECHO OFF - -ECHO. -ECHO. -ECHO This will only work when you have NPM available on the command line -ECHO Works great with NodeJS Portable - https://gareth.flowers/nodejs-portable/ -ECHO. -ECHO. -set /P c=Are you sure you want to continue [Y/N]? -if /I "%c%" EQU "Y" goto :setupgulp -if /I "%c%" EQU "N" goto :eof - -:setupgulp -call npm install -call npm -g install gulp -call npm -g install gulp-cli - -ECHO. -ECHO. -ECHO You should now be able to run: gulp build or gulp dev -ECHO. -ECHO. \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index e5b0dbf201..67bb5a64a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -116,6 +116,13 @@ function isContentCultureVariant() { return $scope.content.variants.length > 1; } + + function reload() { + $scope.page.loading = true; + loadContent().then(function() { + $scope.page.loading = false; + }); + } function bindEvents() { //bindEvents can be called more than once and we don't want to have multiple bound events @@ -123,13 +130,10 @@ eventsService.unsubscribe(evts[e]); } - evts.push(eventsService.on("editors.content.reload", function (name, args) { + evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item - if(args && args.node && args.node.key === $scope.content.key) { - $scope.page.loading = true; - loadContent().then(function() { - $scope.page.loading = false; - }); + if(args && args.documentType && $scope.content.documentType.id === args.documentType.id) { + reload(); } })); @@ -331,8 +335,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid var fieldsToRollback = checkValidility(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 0fdfe6a457..e58ff14e21 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -147,7 +147,7 @@ id: documentType.id, submit: function (model) { const args = { node: scope.node }; - eventsService.emit('editors.content.reload', args); + eventsService.emit("editors.content.reload", args); editorService.close(); }, close: function () { @@ -344,8 +344,8 @@ loadAuditTrail(true); loadRedirectUrls(); setNodePublishStatus(); - updateCurrentUrls(); } + updateCurrentUrls(); }); //ensure to unregister from all events! diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantnotificationlist.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantnotificationlist.directive.js new file mode 100644 index 0000000000..dca7cdbb18 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantnotificationlist.directive.js @@ -0,0 +1,22 @@ +(function () { + 'use strict'; + + function umbNotificationList() { + + var vm = this; + + } + + var umbNotificationListComponent = { + templateUrl: 'views/components/content/umb-variant-notification-list.html', + bindings: { + notifications: "<" + }, + controllerAs: 'vm', + controller: umbNotificationList + }; + + angular.module("umbraco.directives") + .component('umbVariantNotificationList', umbNotificationListComponent); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js index 3a0dbb06d8..ae725ef25e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js @@ -42,7 +42,7 @@ item: '=', onOpen: '&', onOpenAnchor: '&', - index: '@' + hotkey: '<' } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js new file mode 100644 index 0000000000..47381a15c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -0,0 +1,72 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbCheckbox +@restrict E +@scope + +@description +Added in Umbraco version 7.14.0 Use this directive to render an umbraco checkbox. + +

Markup example

+
+    
+ + + + +
+
+ +@param {boolean} model Set to true or false to set the checkbox to checked or unchecked. +@param {string} input-id Set the id of the checkbox. +@param {string} value Set the value of the checkbox. +@param {string} name Set the name of the checkbox. +@param {string} text Set the text for the checkbox label. +@param {string} server-validation-field Set the val-server-field of the checkbox. +@param {boolean} disabled Set the checkbox to be disabled. +@param {boolean} required Set the checkbox to be required. +@param {string} on-change Callback when the value of the checkbox changed by interaction. + +**/ + +(function () { + 'use strict'; + + + function UmbCheckboxController($timeout) { + + var vm = this; + + vm.callOnChange = function() { + $timeout(function() { + vm.onChange({model:vm.model, value:vm.value}); + }, 0); + } + + } + + + var component = { + templateUrl: 'views/components/forms/umb-checkbox.html', + controller: UmbCheckboxController, + controllerAs: 'vm', + bindings: { + model: "=", + inputId: "@", + value: "@", + name: "@", + text: "@", + serverValidationField: "@", + disabled: "<", + required: "<", + onChange: "&" + } + }; + + angular.module('umbraco.directives').component('umbCheckbox', component); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js new file mode 100644 index 0000000000..351ba2fee2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -0,0 +1,57 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbRadiobutton +@restrict E +@scope + +@description +Added in Umbraco version 7.14.0 Use this directive to render an umbraco radio button. + +

Markup example

+
+    
+ + + + +
+
+ +@param {boolean} model Set to true or false to set the radiobutton to checked or unchecked. +@param {string} value Set the value of the radiobutton. +@param {string} name Set the name of the radiobutton. +@param {string} text Set the text for the radiobutton label. +@param {boolean} disabled Set the radiobutton to be disabled. +@param {boolean} required Set the radiobutton to be required. + +**/ + +(function () { + 'use strict'; + + function RadiobuttonDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/forms/umb-radiobutton.html', + scope: { + model: "=", + value: "@", + name: "@", + text: "@", + disabled: "=", + required: "=" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbRadiobutton', RadiobuttonDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 6472dd3d38..dee3cfdab7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -13,25 +13,36 @@ angular.module("umbraco.directives") // TODO: A lot of the code below should be shared between the grid rte and the normal rte var promises = []; - + + //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias + // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because + // we have this mini content editor panel that can be launched with MNTP. + scope.textAreaHtmlId = scope.uniqueId + "_" + String.CreateGuid(); + //queue file loading if (typeof (tinymce) === "undefined") { promises.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope)); } - var toolbar = ["code", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbembeddialog"]; - if (scope.configuration && scope.configuration.toolbar) { - toolbar = scope.configuration.toolbar; + var editorConfig = scope.configuration ? scope.configuration : null; + if (!editorConfig || angular.isString(editorConfig)) { + editorConfig = tinyMceService.defaultPrevalues(); + //for the grid by default, we don't want to include the macro toolbar + editorConfig.toolbar = _.without(editorConfig, "umbmacro"); + } + //make sure there's a max image size + if (!scope.configuration.maxImageSize && scope.configuration.maxImageSize !== 0) { + editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } //stores a reference to the editor var tinyMceEditor = null; promises.push(tinyMceService.getTinyMceEditorConfig({ - htmlId: scope.uniqueId, - stylesheets: scope.configuration ? scope.configuration.stylesheets : null, - toolbar: toolbar, - mode: scope.configuration.mode + htmlId: scope.textAreaHtmlId, + stylesheets: editorConfig.stylesheets, + toolbar: editorConfig.toolbar, + mode: editorConfig.mode })); // pin toolbar to top of screen if we have focus and it scrolls off the screen @@ -46,9 +57,16 @@ angular.module("umbraco.directives") $q.all(promises).then(function (result) { - var tinyMceEditorConfig = result[promises.length - 1]; + var standardConfig = result[promises.length - 1]; - tinyMceEditorConfig.setup = function (editor) { + //create a baseline Config to extend upon + var baseLineConfigObj = { + maxImageSize: editorConfig.maxImageSize + }; + + angular.extend(baseLineConfigObj, standardConfig); + + baseLineConfigObj.setup = function (editor) { //set the reference tinyMceEditor = editor; @@ -111,7 +129,7 @@ angular.module("umbraco.directives") //the elements needed $timeout(function () { tinymce.DOM.events.domLoaded = true; - tinymce.init(tinyMceEditorConfig); + tinymce.init(baseLineConfigObj); }, 150, false); } @@ -132,17 +150,11 @@ angular.module("umbraco.directives") } }); - - //listen for formSubmitting event (the result is callback used to remove the event subscription) - var formSubmittingListener = scope.$on("formSubmitting", function () { - scope.value = tinyMceEditor ? tinyMceEditor.getContent() : null; - }); - + //when the element is disposed we need to unsubscribe! // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom // element might still be there even after the modal has been hidden. scope.$on('$destroy', function () { - formSubmittingListener(); eventsService.unsubscribe(tabShownListener); //ensure we unbind this in case the blur doesn't fire above $('.umb-panel-body').off('scroll', pinToolbar); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js index c3093eee9e..df3770056e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -32,16 +32,27 @@ angular.module("umbraco.directives") restrict: 'E', scope:{ key: '@', - tokens: '=' + tokens: '=', + watchTokens: '@' }, replace: true, link: function (scope, element, attrs) { var key = scope.key; - var tokens = scope.tokens ? scope.tokens : null; - localizationService.localize(key, tokens).then(function(value){ - element.html(value); + scope.text = ""; + + // A render function to be able to update tokens as values update. + function render() { + element.html(localizationService.tokenReplace(scope.text, scope.tokens || null)); + } + + localizationService.localize(key).then(function(value){ + scope.text = value; + render(); }); + if (scope.watchTokens === 'true') { + scope.$watch("tokens", render, true); + } } }; }) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js index 3994770c8e..f226d924af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js @@ -117,9 +117,11 @@ Use this directive to generate a list of content items presented as a flexbox gr }; scope.clickItemName = function(item, $event, $index) { - if(scope.onClickName) { + if(scope.onClickName && !($event.metaKey || $event.ctrlKey)) { scope.onClickName(item, $event, $index); + $event.preventDefault(); } + $event.stopPropagation(); }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 15f5a1d46a..da51528bd2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -1,13 +1,17 @@ (function() { 'use strict'; - function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, localizationService, editorService) { + function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, + dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, + localizationService, editorService, eventsService) { function link(scope, el, attr, ctrl) { - + + var eventBindings = []; var validationTranslated = ""; var tabNoSortOrderTranslated = ""; + scope.dataTypeHasChanged = false; scope.sortingMode = false; scope.toolbar = []; scope.sortableOptionsGroup = {}; @@ -613,18 +617,44 @@ }); }); } - - - var unbindModelWatcher = scope.$watch('model', function(newValue, oldValue) { - if (newValue !== undefined && newValue.groups !== undefined) { - activate(); + + function hasPropertyOfDataTypeId(dataTypeId) { + + // look at each property + var result = _.filter(scope.model.groups, function(group) { + return _.filter(group.properties, function(property) { + return (property.dataTypeId === dataTypeId); + }); + }); + + return (result.length > 0); } - }); - // clean up - scope.$on('$destroy', function(){ - unbindModelWatcher(); - }); + + eventBindings.push(scope.$watch('model', function(newValue, oldValue) { + if (newValue !== undefined && newValue.groups !== undefined) { + activate(); + } + })); + + // clean up + eventBindings.push(eventsService.on("editors.dataTypeSettings.saved", function (name, args) { + if(hasPropertyOfDataTypeId(args.dataType.id)) { + scope.dataTypeHasChanged = true; + } + })); + + // clean up + eventBindings.push(scope.$on('$destroy', function() { + for(var e in eventBindings) { + eventBindings[e](); + } + // if a dataType has changed, we want to notify which properties that are affected by this dataTypeSettings change + if(scope.dataTypeHasChanged === true) { + var args = {documentType: scope.model}; + eventsService.emit("editors.documentType.saved", args); + } + })); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index fb5a558fd0..2f2df7c12b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -56,7 +56,7 @@ Use this directive make an element sticky and follow the page when scrolling. } if (attr.scrollableContainer) { - scrollableContainer = $(attr.scrollableContainer); + scrollableContainer = bar.closest(attr.scrollableContainer); } else { scrollableContainer = $(window); } @@ -121,7 +121,7 @@ Use this directive make an element sticky and follow the page when scrolling. function calculateSize() { var width = bar.innerWidth(); clonedBar.css({ - width: width + width: width + 10 // + 10 (5*2) because we need to add border to avoid seeing the shadow beneath. Look at the CSS. }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index ae41073c0d..3f1929e97d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -117,10 +117,11 @@ var vm = this; vm.clickItem = function (item, $event) { - if (vm.onClick) { + if (vm.onClick && !($event.metaKey || $event.ctrlKey)) { vm.onClick({ item: item}); - $event.stopPropagation(); + $event.preventDefault(); } + $event.stopPropagation(); }; vm.selectItem = function (item, $index, $event) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js index f93e41d0c7..6a8ffa7969 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js @@ -15,10 +15,12 @@ function umbFileUpload() { el.on('change', function (event) { var files = event.target.files; //emit event upward - scope.$emit("filesSelected", { files: files }); + scope.$emit("filesSelected", { files: files }); + //clear the element value - this allows us to pick the same file again and again + el.val(''); }); } }; } -angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); \ No newline at end of file +angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 9ee83dc2ba..61f7f039d9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -27,7 +27,10 @@ function valPropertyMsg(serverValidationManager) { var umbPropCtrl = ctrl[2]; scope.currentProperty = umbPropCtrl.property; - var currentCulture = scope.currentProperty.culture; + + //if the property is invariant (no culture), then we will explicitly set it to the string 'invariant' + //since this matches the value that the server will return for an invariant property. + var currentCulture = scope.currentProperty.culture || "invariant"; var watcher = null; @@ -62,8 +65,8 @@ function valPropertyMsg(serverValidationManager) { if (!watcher) { watcher = scope.$watch("currentProperty.value", function (newValue, oldValue) { - - if (!newValue || angular.equals(newValue, oldValue)) { + + if (angular.equals(newValue, oldValue)) { return; } @@ -78,10 +81,12 @@ function valPropertyMsg(serverValidationManager) { // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg // is the only one, then we'll clear. - if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { + if (errCount === 0 || (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { scope.errorMsg = ""; formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); + } else if (showValidation && scope.errorMsg === "") { + formCtrl.$setValidity('valPropertyMsg', false); + scope.errorMsg = getErrorMsg(); } }, true); } @@ -152,6 +157,7 @@ function valPropertyMsg(serverValidationManager) { showValidation = true; if (hasError && scope.errorMsg === "") { scope.errorMsg = getErrorMsg(); + startWatch(); } else if (!hasError) { scope.errorMsg = ""; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 86867ccff9..4e4c8d2eb5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -344,6 +344,12 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "Import", { file: file })), "Failed to import document type " + file ); + }, + + createDefaultTemplate: function (id) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })), + 'Failed to create default template for content type with id ' + id); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js index 9b1d92f2b0..47ed1f7749 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js @@ -291,84 +291,3 @@ function appState(eventsService) { }; } angular.module('umbraco.services').factory('appState', appState); - -/** - * @ngdoc service - * @name umbraco.services.editorState - * @function - * - * @description - * Tracks the parent object for complex editors by exposing it as - * an object reference via editorState.current.entity - * - * it is possible to modify this object, so should be used with care - */ -angular.module('umbraco.services').factory("editorState", function() { - - var current = null; - var state = { - - /** - * @ngdoc function - * @name umbraco.services.angularHelper#set - * @methodOf umbraco.services.editorState - * @function - * - * @description - * Sets the current entity object for the currently active editor - * This is only used when implementing an editor with a complex model - * like the content editor, where the model is modified by several - * child controllers. - */ - set: function (entity) { - current = entity; - }, - - /** - * @ngdoc function - * @name umbraco.services.angularHelper#reset - * @methodOf umbraco.services.editorState - * @function - * - * @description - * Since the editorstate entity is read-only, you cannot set it to null - * only through the reset() method - */ - reset: function() { - current = null; - }, - - /** - * @ngdoc function - * @name umbraco.services.angularHelper#getCurrent - * @methodOf umbraco.services.editorState - * @function - * - * @description - * Returns an object reference to the current editor entity. - * the entity is the root object of the editor. - * EditorState is used by property/parameter editors that need - * access to the entire entity being edited, not just the property/parameter - * - * editorState.current can not be overwritten, you should only read values from it - * since modifying individual properties should be handled by the property editors - */ - getCurrent: function() { - return current; - } - }; - - // TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. - - //create a get/set property but don't allow setting - Object.defineProperty(state, "current", { - get: function () { - return current; - }, - set: function (value) { - throw "Use editorState.set to set the value of the current entity"; - } - }); - - return state; -}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index b9bc4eb499..1efcf84dcb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -301,6 +301,19 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return allProps; }, + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#buildCompositeVariantId + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns a id for the variant that is unique between all variants on the content + */ + buildCompositeVariantId: function (variant) { + return (variant.language ? variant.language.culture : "invariant") + "_" + (variant.segment ? variant.segment : ""); + }, + /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js new file mode 100644 index 0000000000..5e42af9c5e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -0,0 +1,80 @@ +/** + * @ngdoc service + * @name umbraco.services.editorState + * @function + * + * @description + * Tracks the parent object for complex editors by exposing it as + * an object reference via editorState.current.entity + * + * it is possible to modify this object, so should be used with care + */ +angular.module('umbraco.services').factory("editorState", function() { + + var current = null; + var state = { + + /** + * @ngdoc function + * @name umbraco.services.angularHelper#set + * @methodOf umbraco.services.editorState + * @function + * + * @description + * Sets the current entity object for the currently active editor + * This is only used when implementing an editor with a complex model + * like the content editor, where the model is modified by several + * child controllers. + */ + set: function (entity) { + current = entity; + }, + + /** + * @ngdoc function + * @name umbraco.services.angularHelper#reset + * @methodOf umbraco.services.editorState + * @function + * + * @description + * Since the editorstate entity is read-only, you cannot set it to null + * only through the reset() method + */ + reset: function() { + current = null; + }, + + /** + * @ngdoc function + * @name umbraco.services.angularHelper#getCurrent + * @methodOf umbraco.services.editorState + * @function + * + * @description + * Returns an object reference to the current editor entity. + * the entity is the root object of the editor. + * EditorState is used by property/parameter editors that need + * access to the entire entity being edited, not just the property/parameter + * + * editorState.current can not be overwritten, you should only read values from it + * since modifying individual properties should be handled by the property editors + */ + getCurrent: function() { + return current; + } + }; + + // TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. + + //create a get/set property but don't allow setting + Object.defineProperty(state, "current", { + get: function () { + return current; + }, + set: function (value) { + throw "Use editorState.set to set the value of the current entity"; + } + }); + + return state; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index 6862b11565..8aaddbf98f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -59,11 +59,11 @@ * Method for internal use, based on the collection of layouts passed, the method selects either * any previous layout from local storage, or picks the first allowed layout * - * @param {Number} nodeId The id of the current node displayed in the content editor + * @param {Any} id The identifier of the current node or application displayed in the content editor * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts */ - function getLayout(nodeId, availableLayouts) { + function getLayout(id, availableLayouts) { var storedLayouts = []; @@ -74,8 +74,8 @@ if (storedLayouts && storedLayouts.length > 0) { for (var i = 0; storedLayouts.length > i; i++) { var layout = storedLayouts[i]; - if (layout.nodeId === nodeId) { - return setLayout(nodeId, layout, availableLayouts); + if (isMatchingLayout(id, layout)) { + return setLayout(id, layout, availableLayouts); } } @@ -93,12 +93,12 @@ * @description * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage * - * @param {Number} nodeID Id of the current node displayed in the content editor + * @param {Any} id The identifier of the current node or application displayed in the content editor * @param {Object} selectedLayout Layout selected as the layout to set as the current layout * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts */ - function setLayout(nodeId, selectedLayout, availableLayouts) { + function setLayout(id, selectedLayout, availableLayouts) { var activeLayout = {}; var layoutFound = false; @@ -118,7 +118,7 @@ activeLayout = getFirstAllowedLayout(availableLayouts); } - saveLayoutInLocalStorage(nodeId, activeLayout); + saveLayoutInLocalStorage(id, activeLayout); return activeLayout; @@ -132,11 +132,11 @@ * @description * Stores a given layout as the current default selection in local storage * - * @param {Number} nodeId Id of the current node displayed in the content editor + * @param {Any} id The identifier of the current node or application displayed in the content editor * @param {Object} selectedLayout Layout selected as the layout to set as the current layout */ - function saveLayoutInLocalStorage(nodeId, selectedLayout) { + function saveLayoutInLocalStorage(id, selectedLayout) { var layoutFound = false; var storedLayouts = []; @@ -147,7 +147,7 @@ if (storedLayouts.length > 0) { for (var i = 0; storedLayouts.length > i; i++) { var layout = storedLayouts[i]; - if (layout.nodeId === nodeId) { + if (isMatchingLayout(id, layout)) { layout.path = selectedLayout.path; layoutFound = true; } @@ -156,7 +156,7 @@ if (!layoutFound) { var storageObject = { - "nodeId": nodeId, + "id": id, "path": selectedLayout.path }; storedLayouts.push(storageObject); @@ -397,6 +397,54 @@ } } + + + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#selectAllItemsToggle + * @methodOf umbraco.services.listViewHelper + * + * @description + * Helper method for toggling the select state on all items. + * + * @param {Array} items Items to toggle selection on, should be $scope.items + * @param {Array} selection Listview selection, available as $scope.selection + */ + + function selectAllItemsToggle(items, selection) { + + if (!angular.isArray(items)) { + return; + } + + if (isSelectedAll(items, selection)) { + // unselect all items + angular.forEach(items, function (item) { + item.selected = false; + }); + + // reset selection without loosing reference. + selection.length = 0; + + } else { + + // reset selection without loosing reference. + selection.length = 0; + + // select all items + angular.forEach(items, function (item) { + var obj = { + id: item.id + }; + if (item.key) { + obj.key = item.key; + } + item.selected = true; + selection.push(obj); + }); + } + + } /** * @ngdoc method @@ -510,6 +558,12 @@ }; } + + function isMatchingLayout(id, layout) { + // legacy format uses "nodeId", be sure to look for both + return layout.id === id || layout.nodeId === id; + } + var service = { getLayout: getLayout, @@ -521,6 +575,7 @@ deselectItem: deselectItem, clearSelection: clearSelection, selectAllItems: selectAllItems, + selectAllItemsToggle: selectAllItemsToggle, isSelectedAll: isSelectedAll, setSortingDirection: setSortingDirection, setSorting: setSorting, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 2a9afdfa94..ea2ad73263 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -43,16 +43,11 @@ angular.module('umbraco.services') var entry = dictionary[value]; if (entry) { - if (tokens) { - for (var i = 0; i < tokens.length; i++) { - entry = entry.replace("%" + i + "%", tokens[i]); - } - } - return entry; + return service.tokenReplace(entry, tokens); } return "[" + value + "]"; } - + var service = { // array to hold the localized resource string entries dictionary: [], @@ -127,7 +122,29 @@ angular.module('umbraco.services') } return value; }, - + + + /** + * @ngdoc method + * @name umbraco.services.localizationService#tokenReplace + * @methodOf umbraco.services.localizationService + * + * @description + * Helper to replace tokens + * @param {String} value the text-string to manipulate + * @param {Array} tekens An array of tokens values + * @returns {String} Replaced test-string + */ + tokenReplace: function (text, tokens) { + if (tokens) { + for (var i = 0; i < tokens.length; i++) { + text = text.replace("%" + i + "%", tokens[i]); + } + } + return text; + }, + + /** * @ngdoc method * @name umbraco.services.localizationService#localize @@ -146,8 +163,7 @@ angular.module('umbraco.services') */ localize: function (value, tokens) { return service.initLocalizedResources().then(function (dic) { - var val = _lookup(value, tokens, dic); - return val; + return _lookup(value, tokens, dic); }); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 76fcc2d1d8..ba8334d307 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -26,6 +26,8 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve navReadyPromise.resolve(mainTreeApi); }); + + //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index 2575b05bb6..c123ac6cea 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -85,17 +85,17 @@ angular.module('umbraco.services') nArray.push(item); if(!item.sticky) { - $timeout(function() { - var found = _.find(nArray, function(i) { - return i.id === item.id; - }); - - if (found) { - var index = nArray.indexOf(found); - nArray.splice(index, 1); - } - - }, 7000); + $timeout( + function() { + var found = _.find(nArray, function(i) { + return i.id === item.id; + }); + if (found) { + var index = nArray.indexOf(found); + nArray.splice(index, 1); + } + } + , 10000); } return item; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 4fe7f8b7d7..ce4bf6077c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -427,8 +427,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s data["rel"] = img.id; data["data-id"] = img.id; } - - editor.insertContent(editor.dom.createHTML('img', data)); + + editor.selection.setContent(editor.dom.createHTML('img', data)); $timeout(function () { var imgElm = editor.dom.get('__mcenew'); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 4650458abc..6a15c0f553 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -350,6 +350,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS parent.children.splice(parent.children.indexOf(treeNode), 1); parent.hasChildren = parent.children.length !== 0; + + //Notify that the node has been removed + eventsService.emit("treeService.removeNode", { node: treeNode }); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index cbe9b561a0..a03a71febe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -365,7 +365,7 @@ action: action, variants: _.map(displayModel.variants, function(v) { return { - name: v.name, + name: v.name || "", //if its null/empty,we must pass up an empty string else we get json converter errors properties: getContentProperties(v.tabs), culture: v.language ? v.language.culture : null, publish: v.publish, diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 8e5c093462..c426d0d955 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -9,7 +9,7 @@ * * @param {navigationService} navigationService A reference to the navigationService */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) { +function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) { //this is used to trigger the tree to start loading once everything is ready var treeInitPromise = $q.defer(); @@ -249,6 +249,20 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar $scope.infiniteMode = args && args.editors.length > 0 ? true : false; })); + evts.push(eventsService.on("treeService.removeNode", function (e, args) { + //check to see if the current page has been removed + + var currentEditorState = editorState.getCurrent() + if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) { + //current page is loaded, so navigate to root + var section = appState.getSectionState("currentSection"); + $location.path("/" + section); + } + })); + + + + /** * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down */ diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 9458b11f46..780ec2fd3b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -117,6 +117,7 @@ @import "components/umb-confirm-action.less"; @import "components/umb-keyboard-shortcuts-overview.less"; @import "components/umb-checkbox-list.less"; +@import "components/umb-form-check.less"; @import "components/umb-locked-field.less"; @import "components/umb-tabs.less"; @import "components/umb-load-indicator.less"; @@ -136,10 +137,12 @@ @import "components/umb-iconpicker.less"; @import "components/umb-insert-code-box.less"; @import "components/umb-packages.less"; +@import "components/umb-logviewer.less"; @import "components/umb-package-local-install.less"; @import "components/umb-panel-group.less"; @import "components/umb-lightbox.less"; @import "components/umb-avatar.less"; +@import "components/umb-readonlyvalue.less"; @import "components/umb-progress-bar.less"; @import "components/umb-querybuilder.less"; @import "components/umb-pagination.less"; @@ -166,6 +169,7 @@ @import "components/umb-property-file-upload.less"; @import "components/users/umb-user-cards.less"; +@import "components/users/umb-user-table.less"; @import "components/users/umb-user-details.less"; @import "components/users/umb-user-group-picker-list.less"; @import "components/users/umb-user-group-preview.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index 4c30ae583c..6f23677a1c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -10,47 +10,111 @@ } } -.umb-toggle:focus .umb-toggle__toggle{ - box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); -} - -.umb-toggle__handler { - position: absolute; - top: 0; - left: 0; - display: block; - width: 24px; - height: 24px; - background-color: @white; - border-radius: 50px; - transform: rotate(-45deg); -} - .umb-toggle__toggle { cursor: pointer; align-items: center; display: flex; - width: 48px; - height: 24px; - background: @gray-8; - border-radius: 90px; + width: 38px; + height: 18px; + border-radius: 10px; + border: 1px solid @inputBorder; + background-color: @inputBorder; position: relative; - transition: box-shadow .3s; + transition: background-color 120ms; + + .umb-toggle:hover &, + .umb-toggle:focus & { + border-color: @inputBorderFocus; + } + + .umb-toggle.umb-toggle--checked & { + border-color: @ui-btn; + background-color: @ui-btn; + + &:hover { + background-color: @ui-btn-hover; + } + } + + .umb-toggle.umb-toggle--checked:focus & { + border-color: black; + } } -.umb-toggle--checked .umb-toggle__toggle { - background-color: @green; + + + +.umb-toggle__handler { + position: absolute; + top: 1px; + left: 1px; + display: block; + width: 16px; + height: 16px; + background-color: @white; + border-radius: 8px; + transition: transform 120ms ease-in-out, background-color 120ms; + + .umb-toggle.umb-toggle--checked & { + transform: translateX(20px); + background-color: white; + } + } -.umb-toggle--disabled .umb-toggle__toggle { - cursor: not-allowed; - opacity: 0.8; + +/* Icons */ + +.umb-toggle__icon { + position: absolute; + font-size: 12px; + line-height: 1em; + text-decoration: none; + transition: all 0.2s ease; } -.umb-toggle--checked .umb-toggle__handler { - transform: translate3d(24px, 0, 0) rotate(0); +.umb-toggle__icon--left { + left: 5px; + color: white; + transition: opacity 120ms; + opacity: 0; + // .umb-toggle:hover & { + // color: @ui-btn-hover; + // } + .umb-toggle--checked & { + opacity: 1; + } + .umb-toggle.umb-toggle--checked:hover & { + color: white; + } } +.umb-toggle__icon--right { + right: 5px; + color: @ui-btn; + transition: opacity 120ms; + .umb-toggle--checked & { + opacity: 0; + } + .umb-toggle:hover & { + color: @ui-btn-hover; + } +} + + + + +.umb-toggle.umb-toggle--disabled { + .umb-toggle__toggle { + cursor: not-allowed; + border-color: @gray-5; + } + .umb-toggle__handler { + background-color: @gray-5; + } +} + + /* Labels */ .umb-toggle__label { @@ -64,22 +128,3 @@ .umb-toggle__label--right { margin-left: 8px; } - -/* Icons */ - -.umb-toggle__icon { - position: absolute; - line-height: 1em; - text-decoration: none; - transition: all 0.2s ease; -} - -.umb-toggle__icon--left { - left: 7px; - color: @white; -} - -.umb-toggle__icon--right { - right: 7px; - color: @gray-5; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 0770a895c0..9d0e0d93a1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -90,6 +90,9 @@ .umb-editor-header__name-and-description { margin-right: 20px; + .umb-panel-header-description { + padding: 0 10px; + } } .-split-view-active .umb-editor-header__name-and-description { @@ -166,24 +169,30 @@ a.umb-editor-header__close-split-view:hover { height: 30px; text-decoration: none !important; font-size: 13px; - //color: @gray-4; - color: @ui-action-disgrete-type; - //background-color: @white; + color: @ui-action-discreet-type; + + max-width: 50%; + white-space: nowrap; + + span { + text-overflow: ellipsis; + overflow: hidden; + } } a.umb-variant-switcher__toggle { transition: color 0.2s ease-in-out; &:hover { //background-color: @gray-10; - color: @ui-action-disgrete-type-hover; + color: @ui-action-discreet-type-hover; .umb-variant-switcher__expand { - color: @ui-action-disgrete-type-hover; + color: @ui-action-discreet-type-hover; } } } .umb-variant-switcher__expand { - color: @ui-action-disgrete-type; + color: @ui-action-discreet-type; margin-top: 3px; margin-left: 5px; margin-right: -5px; @@ -195,6 +204,7 @@ a.umb-variant-switcher__toggle { justify-content: space-between; align-items: center; border-bottom: 1px solid @gray-9; + position: relative; &:hover .umb-variant-switcher__name-wrapper { } @@ -246,10 +256,15 @@ a.umb-variant-switcher__toggle { } .umb-variant-switcher__split-view { - font-size: 13px; - display: none; - padding: 16px 20px; - + font-size: 13px; + display: none; + padding: 16px 20px; + position: absolute; + right: 0; + top: 0; + bottom: 0; + background-color: @white; + &:hover { background-color: @ui-option-hover; color: @ui-option-type-hover; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 0c11349841..2e599252bb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -1,7 +1,11 @@ .umb-editor-sub-header { padding: 10px 0; margin-bottom: 10px; - background: @gray-10; + background: @brownGrayLight; + border-left: 5px solid @brownGrayLight; + border-right: 5px solid @brownGrayLight; + margin-left: -5px; + margin-right: -5px; display: flex; justify-content: space-between; margin-top: -10px; @@ -12,7 +16,7 @@ &.nested { margin-top: 0; margin-bottom: 0; - background: @gray-10; + background: @brownGrayLight; } } @@ -25,10 +29,14 @@ .umb-editor-sub-header.-umb-sticky-bar { box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); - transition: box-shadow 1s; - top: calc(@appHeaderHeight + @editorHeaderHeight); + transition: box-shadow 240ms; margin-top: 0; margin-bottom: 0; + top: calc(@appHeaderHeight + @editorHeaderHeight); + + .umb-editor--infinityMode & { + top: calc(@editorHeaderHeight); + } } .umb-group-builder__property-preview .umb-editor-sub-header { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less index 9e16eca414..7f04fef9a9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less @@ -22,7 +22,14 @@ font-size: 14px; border: none; position: relative; - margin-bottom: 0; + border-radius: 10px; + margin: 10px; + + .close { + top: 0; + right: -6px; + opacity: 0.4; + } } .umb-notifications__notification.-extra-padding { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 6e9c79a0c2..9205dc9c5f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -250,3 +250,11 @@ .form-horizontal .umb-overlay .controls { margin-left: 0 !important; } + +.umb-overlay .text-error { + color: @formErrorText; +} + +.umb-overlay .text-success { + color: @formSuccessText; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip.less b/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip.less index 77eba94b6d..5158d32304 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip.less @@ -11,4 +11,5 @@ animation: fadeIn; margin-top: 15px; pointer-events: none; + border-radius: 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 8d740a866c..b66ab40335 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -166,7 +166,9 @@ body.touch .umb-tree { } .umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class^="icon-"], -.umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class*=" icon-"] { +.umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class*=" icon-"], +.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class^="icon-"], +.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class*=" icon-"] { font-family: 'icomoon' !important; color: @green !important; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less index 02f30f6f35..985e57bea5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less @@ -1,3 +1,6 @@ +@checkboxWidth: 15px; +@checkboxHeight: 15px; + .umb-checkbox-list { list-style: none; margin-left: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index d2fa2be0c7..84cfe04263 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -13,24 +13,37 @@ user-select: none; box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); border-radius: 3px; - } -.umb-content-grid__item.-selected { - &::before { - content: ""; - position: absolute; - z-index:2; - top: -2px; - left: -2px; - right: -2px; - bottom: -2px; - border: 2px solid @ui-selected-border; - border-radius: 5px; - box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); - pointer-events: none; +.umb-content-grid__item { + &.-selected, &:hover { + &::before { + content: ""; + position: absolute; + z-index: 2; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid @ui-selected-border; + border-radius: 5px; + box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); + pointer-events: none; + } + + } +} + + +.umb-content-grid__item:hover { + &::before { + opacity: .33; + } +} +.umb-content-grid__item.-selected:hover { + &::before { + opacity: .75; } - } .umb-content-grid__icon-container { @@ -59,7 +72,8 @@ display: inline-flex; color: @ui-option-type; - &:hover { + &:hover, &:focus { + text-decoration: none; color:@ui-option-type-hover; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less index 09c8fa9aa0..e6b3fdbfa9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less @@ -61,6 +61,9 @@ .show-validation .umb-sub-views-nav-item > a.-has-error { color: @red; + &::after { + background-color: @red; + } } .umb-sub-views-nav-item .icon { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less index b5021d7c38..5071091fcc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -41,13 +41,13 @@ // file select link .file-select { font-size: 15px; - color: @ui-action-disgrete-type; + color: @ui-action-discreet-type; cursor: pointer; margin-top: 10px; &:hover { - color: @ui-action-disgrete-type-hover; + color: @ui-action-discreet-type-hover; text-decoration: none; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less index ffdd4d9322..6a93a10e9f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -27,7 +27,12 @@ .umb-folder-grid__folder.-selected { color:@ui-selected-type; - + &:hover { + color:@ui-selected-type-hover; + } +} + +.umb-folder-grid__folder.-selected, .umb-folder-grid__folder:hover { &::before { content: ""; position: absolute; @@ -41,9 +46,15 @@ box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); pointer-events: none; } - - &:hover { - color:@ui-selected-type-hover; +} +.umb-folder-grid__folder:hover { + &::before { + opacity: .33; + } +} +.umb-folder-grid__folder.-selected:hover { + &::before { + opacity: .75; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less new file mode 100644 index 0000000000..fbc9cd8f97 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -0,0 +1,136 @@ +@checkboxWidth: 16px; +@checkboxHeight: 16px; + +.umb-form-check { + display: flex; + flex-wrap: wrap; + align-items: center; + position: relative; + padding: 0; + margin: 0; + min-height: 22px; + line-height: 22px; + cursor: pointer !important; + + &__text { + margin: 0 0 0 26px; + position: relative; + top: 0; + } + + &__input { + position: absolute; + top: 0; + left: 0; + opacity: 0; + + &:checked ~ .umb-form-check__state .umb-form-check__check { + border-color: @ui-option-type; + } + + &:focus:checked ~ .umb-form-check .umb-form-check__check, + &:focus ~ .umb-form-check__state .umb-form-check__check { + border-color: @inputBorderFocus; + } + + &:checked ~ .umb-form-check__state { + .umb-form-check__check { + // This only happens if the state has a radiobutton modifier + .umb-form-check--radiobutton & { + &:before { + opacity: 1; + transform: scale(1); + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + &:before { + width: @checkboxWidth; + height: @checkboxHeight; + } + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + .umb-form-check__icon { + opacity: 1; + } + } + } + } + + &__state { + display: flex; + height: 18px; + margin-top: 2px; + position: absolute; + top: 0; + left: 0; + } + + &__check { + display: flex; + position: relative; + background: @white; + border: 1px solid @inputBorder; + width: @checkboxWidth; + height: @checkboxHeight; + + &:before { + content: ""; + background: @ui-option-type; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + } + // This only happens if state has the radiobutton modifier + .umb-form-check--radiobutton & { + border-radius: 100%; + + &:before { + width: 10px; + height: 10px; + border-radius: 100%; + opacity: 0; + transform: scale(0); + transition: .15s ease-out; + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + &:before { + width: 0; + height: 0; + transition: .05s ease-out; + } + } + } + + &__icon { + color: @white; + text-align: center; + font-size: 12px; + opacity: 0; + transition: .2s ease-out; + + &:before { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + margin: auto; + } + } + + &.umb-form-check--disabled { + cursor: not-allowed !important; + opacity: 0.5; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index d6db204ff2..b5abbe06bc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -128,9 +128,10 @@ position: relative; margin-bottom: 40px; padding-top: 10px; + border: 1px solid @grayLighter; &:hover { - background-color: @grayLighter; + border-color: @grayLight; } &[ng-click], @@ -161,15 +162,16 @@ } .umb-grid .umb-row .umb-cell-placeholder { - min-height: 130px; - background-color: @gray-10; - border-width: 2px; + min-height: 88px; + border-width: 1px; border-style: dashed; - border-color: @gray-8; + border-color: @ui-action-discreet-border; + color: @ui-action-discreet-type; transition: border-color 100ms linear; &:hover { - border-color: @blueMid; + border-color: @ui-action-discreet-border-hover; + color: @ui-action-discreet-type-hover; cursor: pointer; } } @@ -207,9 +209,9 @@ } .umb-grid .cell-tools-add { - color: @ui-action-type; + color: @ui-action-discreet-type; &:focus, &:hover { - color: @ui-action-type-hover; + color: @ui-action-discreet-type-hover; text-decoration: none; } } @@ -219,16 +221,18 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - color: @ui-action-type; + color: @ui-action-discreet-type; } .umb-grid .cell-tools-add.-bar { display: block; - background: @gray-10; text-align: center; padding: 5px; - border: 1px dashed @gray-7; + border: 1px dashed @ui-action-discreet-border; margin: 10px; + &:focus, &:hover { + border-color: @ui-action-discreet-border-hover; + } } @@ -249,7 +253,6 @@ .umb-grid .cell-tools-edit { display: inline-block; - color: @white; } .umb-grid .drop-overlay { @@ -412,36 +415,17 @@ // Row states .umb-grid .umb-row.-active { - background-color: @ui-action-type; + border-color: @ui-action-type; .umb-row-title-bar { cursor: move; } - - .row-tool, - .umb-row-title { - color: @white; - } - - .umb-grid-has-config { - color: fade(@white, 66); - } - - .umb-cell { - .umb-grid-has-config { - color: fade(@black, 44); - } - } - - .umb-cell .umb-cell-content { - border-color: transparent; - } } .umb-grid .umb-row.-active-child { background-color: @gray-10; - + .umb-row-title-bar { cursor: inherit; } @@ -449,22 +433,7 @@ .umb-row-title { color: @gray-3; } - - .row-tool { - color: fade(@black, 23); - } - - .umb-grid-has-config { - color: fade(@black, 44); - } - - .umb-cell-content.-placeholder { - border-color: @gray-8; - - &:hover { - border-color: fade(@gray, 44); - } - } + } @@ -573,14 +542,13 @@ display: inline-block; cursor: pointer; border-radius: 200px; - background: @gray-10; - border: 1px solid @gray-7; + border: 1px solid @ui-action-discreet-border; margin: 2px; &:hover, &:hover * { - background: @ui-action-type-hover !important; + background: @ui-action-discreet-type-hover !important; color: @white !important; - border-color: @ui-action-type-hover !important; + border-color: @ui-action-discreet-border-hover !important; text-decoration: none; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index d8c7224d57..e2a35c5fb5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -75,7 +75,7 @@ display: flex; align-items: center; border-bottom: 1px solid @gray-9; - padding: 10px 15px; + padding: 10px 15px 10px 10px; } .umb-group-builder__group-title { @@ -112,7 +112,7 @@ input.umb-group-builder__group-title-input { } input.umb-group-builder__group-title-input:disabled:hover { - border: none; + border-color: transparent; } .umb-group-builder__group-title-input:hover { @@ -135,12 +135,35 @@ input.umb-group-builder__group-title-input:disabled:hover { margin-left: auto; } +.umb-group-builder__group-add-property { + min-height: 46px; + margin-right: 30px; + margin-left: 270px; + border-radius: 3px; + + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + border: 1px dashed @gray-7; + background-color: transparent; + color: @ui-action-type; + font-weight: bold; + position: relative; + &:hover { + color:@ui-action-type-hover; + text-decoration: none; + border-color: @ui-active-type-hover; + } +} + /* ---------- PROPERTIES ---------- */ .umb-group-builder__properties { list-style: none; margin: 0; padding: 15px; + padding-right: 5px; min-height: 35px; // the height of a sortable property } @@ -228,6 +251,9 @@ input.umb-group-builder__group-title-input:disabled:hover { overflow: hidden; border-color: transparent; background: transparent; + &:focus { + border-color: @inputBorderFocus; + } } .umb-group-builder__property-meta-label textarea.ng-invalid { @@ -247,6 +273,9 @@ input.umb-group-builder__group-title-input:disabled:hover { overflow: hidden; border-color: transparent; background: transparent; + &:focus { + border-color: @inputBorderFocus; + } } .umb-group-builder__property-preview { @@ -265,21 +294,17 @@ input.umb-group-builder__group-title-input:disabled:hover { bottom: 0; left: 0; right: 0; - background: rgba(0,0,0,0.1); + background: rgba(225,225,225,.5); transition: opacity 120ms; } } -.umb-group-builder__property-preview:hover { +.umb-group-builder__property-preview:not(.-not-clickable):hover { &::after { - opacity: .8; + opacity: .66; } } -.umb-group-builder__property-preview:focus { - outline: none; -} - .umb-group-builder__property-preview.-not-clickable { cursor: auto; } @@ -301,23 +326,54 @@ input.umb-group-builder__group-title-input:disabled:hover { opacity: 0.8 } + +.umb-group-builder__open-settings { + position: absolute; + z-index:1; + top: 0; + bottom:0; + left: 0; + width: 100%; + background-color: transparent; + border: none; + &:focus { + outline:0; + border: 1px solid @inputBorderFocus; + } +} + .umb-group-builder__property-actions { - flex: 0 0 40px; - text-align: center; - margin: 15px 0 0 15px; + flex: 0 0 44px; + text-align: right; + margin-top: 7px; } .umb-group-builder__property-action { - margin: 0 0 10px 0; - display: block; - font-size: 18px; - position: relative; - cursor: pointer; - color: @ui-icon; -} - -.umb-group-builder__property-action:hover { - color: @ui-icon-hover; + + position: relative; + margin: 5px 0; + + > button { + border: none; + + font-size: 18px; + position: relative; + cursor: pointer; + color: @ui-icon; + + margin: 0; + padding: 5px 10px; + width: auto; + overflow: visible; + background: transparent; + line-height: normal; + outline: 0; + -webkit-appearance: none; + + &:hover, &:focus { + color: @ui-icon-hover; + } + } } .umb-group-builder__property-tags { @@ -439,7 +495,7 @@ input.umb-group-builder__group-sort-value { font-size: 14px; color: @ui-action-type; - &:hover { + &:hover{ text-decoration: none; color:@ui-action-type-hover; border-color:@ui-action-border-hover; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less index 846b7d7aee..bfe41ccaac 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less @@ -22,6 +22,10 @@ a.umb-list-item:focus { opacity: 0.6; } +.umb-list-item--error { + color: @red; +} + .umb-list-item:hover .umb-list-checkbox, .umb-list-item--selected .umb-list-checkbox { opacity: 1; @@ -34,4 +38,4 @@ a.umb-list-item:focus { .umb-list-checkbox--visible { opacity: 1; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less new file mode 100644 index 0000000000..f7aa0e4558 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -0,0 +1,42 @@ +/* PACKAGE DETAILS */ + +.umb-logviewer { + display: flex; + flex-flow: row wrap; +} + +@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less + +.umb-logviewer__main-content { + flex: 1 1 auto; + margin-right: 20px; + width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling + min-width: 500px; + + .btn-link { + text-align: left; + } +} + +.umb-logviewer__sidebar { + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-logviewer { + flex-direction: column; + } + + .umb-logviewer__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-logviewer__sidebar { + flex: 1 1 auto; + width: 100%; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 7a6cd043a4..68973b4c7c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -38,12 +38,12 @@ } .umb-media-grid__item.-selected { - - //background: @ui-selected; color:@ui-selected-type; - //border-color: @ui-selected-border; - //box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); - + .umb-media-grid__item-overlay { + color: @ui-selected-type; + } +} +.umb-media-grid__item.-selected, .umb-media-grid__item:hover { &::before { content: ""; position: absolute; @@ -57,12 +57,16 @@ box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); pointer-events: none; } - - .umb-media-grid__item-overlay { - color: @ui-selected-type; - //background: @ui-selected-border; +} +.umb-media-grid__item:hover { + &::before { + opacity: .33; + } +} +.umb-media-grid__item.-selected:hover { + &::before { + opacity: .75; } - } .umb-media-grid__item-file-icon > span { @@ -158,6 +162,9 @@ .umb-media-grid__item:hover .umb-media-grid__item-overlay { opacity: 1; + i { + text-decoration: none; + } } .umb-media-grid__item-name { @@ -211,7 +218,7 @@ transition: opacity 150ms; &:hover { - color: @ui-action-disgrete-type-hover; + color: @ui-action-discreet-type-hover; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index b054ddf907..d52cb918f6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -89,16 +89,16 @@ display: flex; align-items: center; justify-content: center; - border: 1px dashed @ui-action-disgrete-border; - color: @ui-action-disgrete-type; + border: 1px dashed @ui-action-discreet-border; + color: @ui-action-discreet-type; font-weight: bold; padding: 5px 15px; box-sizing: border-box; } .umb-node-preview-add:hover { - color: @ui-action-disgrete-type-hover; - border-color: @ui-action-disgrete-border-hover; + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; text-decoration: none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index 45170c9602..f704dd48e2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -261,6 +261,7 @@ .umb-package-details { display: flex; + flex-flow: row wrap; } a.umb-package-details__back-link { @@ -280,6 +281,7 @@ a.umb-package-details__back-link { flex: 1 1 auto; margin-right: 20px; width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling + min-width: 500px; } .umb-package-details__sidebar { @@ -406,7 +408,7 @@ a.umb-package-details__back-link { .umb-gallery__thumbnail { flex: 0 1 100px; - border: 1px solid @ui-action-disgrete-border; + border: 1px solid @ui-action-discreet-border; border-radius: 3px; margin: 5px; padding: 10px; @@ -416,7 +418,7 @@ a.umb-package-details__back-link { .umb-gallery__thumbnail:hover { cursor: pointer; - border-color: @ui-action-disgrete-border-hover; + border-color: @ui-action-discreet-border-hover; } /* PACKAGE LIST */ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less new file mode 100644 index 0000000000..0790bdd07a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less @@ -0,0 +1,3 @@ +.umb-readonlyvalue { + position:relative; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 291eef43e0..6674e01475 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -91,12 +91,39 @@ input.umb-table__input { .umb-table-body .umb-table-row { color: @gray-5; border-top: 1px solid @gray-9; - cursor: pointer; font-size: 14px; position: relative; min-height: 52px; +} +.umb-table-body .umb-table-row.-selectable { + cursor: pointer; +} +.umb-table-row.-selected, +.umb-table-body .umb-table-row.-selectable:hover { + &::before { + content: ""; + position: absolute; + z-index:1; + top: 1px; + left: 1px; + right: 1px; + bottom: 1px; + border: 2px solid @ui-selected-border; + pointer-events: none; + } +} +.umb-table-body .umb-table-row.-selectable { &:hover { - background-color: @ui-option-hover; + &::before { + opacity:.33; + } + } +} +.umb-table-body .umb-table-row.-selected.-selectable { + &:hover { + &::before { + opacity:.66; + } } } @@ -122,7 +149,7 @@ input.umb-table__input { margin: 0 auto; font-size: 20px; line-height: 20px; - color: @gray-7; + color: @ui-option-type; } .umb-table-body__checkicon, @@ -135,9 +162,30 @@ input.umb-table__input { } .umb-table-body .umb-table__name { - color: @black; + color: @ui-option-type; font-size: 14px; font-weight: bold; + a { + color: @ui-option-type; + &:hover, &:focus { + color: @ui-option-type-hover; + text-decoration: underline; + } + } +} +.umb-table-body .umb-table-row.-light { + .umb-table-body__icon { + color: @ui-option-disabled-type; + } + .umb-table__name { + color: @ui-option-disabled-type; + a { + color: @ui-option-disabled-type; + &:hover, &:focus { + color: @ui-option-disabled-type-hover; + } + } + } } // Styles of no items in the listview @@ -150,27 +198,15 @@ input.umb-table__input { } // Show checkmark when checked, hide file icon -.umb-table-row--selected { - /* +.umb-table-row.-selected { + .umb-table-body__fileicon { display: none; } .umb-table-body__checkicon { display: inline-block; } - */ - &::before { - content: ""; - position: absolute; - z-index:1; - top: 1px; - left: 1px; - right: 1px; - bottom: 1px; - border: 2px solid @ui-selected-border; - box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%); - pointer-events: none; - } + } // Table Row Styles diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index e24f68078b..3bc431e01f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -17,7 +17,11 @@ outline: none; text-decoration: none !important; } -.umb-user-card.-selected { + +.umb-user-card.-selectable { + cursor: pointer; +} +.umb-user-card.-selected, .umb-user-card.-selectable:hover { &::before { content: ""; position: absolute; @@ -26,12 +30,21 @@ left: -2px; right: -2px; bottom: -2px; - border: 2px solid @ui-selected-border; border-radius: 5px; - box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); pointer-events: none; + border: 2px solid @ui-selected-border; + box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); + } +} +.umb-user-card.-selectable:hover { + &::before { + opacity: .33; + } +} +.umb-user-card.-selected:hover { + &::before { + opacity: .75; } - } .umb-user-card__content { @@ -44,14 +57,18 @@ box-sizing: border-box; display: flex; flex-direction: column; - cursor: pointer; max-width: 100%; } .umb-user-card__goToUser { - &:hover { + &:hover, &:focus { + text-decoration: none; .umb-user-card__name { - text-decoration: underline; + text-decoration: underline; + color: @ui-option-type-hover; + } + .umb-avatar { + border: 1px solid @ui-option-type-hover; } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less new file mode 100644 index 0000000000..bc85ae90a9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less @@ -0,0 +1,74 @@ +.umb-user-table { + + .umb-user-table-col-avatar { + flex: 0 0 32px; + padding: 15px 0; + } + + .umb-table-cell a { + &:hover, &:focus { + .umb-avatar { + border: 1px solid @ui-option-type-hover; + } + } + } + + .umb-table-body .umb-table-cell.umb-table__name { + margin: 0; + padding: 0; + a { + display: flex; + padding: 6px 2px; + height: 42px; + span { + margin: auto 14px; + } + } + } + .umb-table-cell.umb-table__name a { + &:hover, &:focus { + text-decoration: underline; + } + } + + .umb-user-table-row { + .umb-checkmark { + visibility: hidden; + } + } + + &.-has-selection { + .umb-user-table-row.-selectable { + .umb-checkmark { + visibility: visible; + } + } + } + + .umb-user-table-row.-selectable:hover { + .umb-checkmark { + visibility: visible; + } + } + + .umb-user-table-row.-selected { + + .umb-checkmark { + visibility: visible; + } + + &::before { + content: ""; + position: absolute; + z-index:1; + top: 1px; + left: 1px; + right: 1px; + bottom: 1px; + border: 2px solid @ui-selected-border; + box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%); + pointer-events: none; + } + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 45188ca02e..dc63baf335 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -533,6 +533,10 @@ div.help { } +table.domains .help-inline { + color:@red; +} + // INPUT GROUPS // ------------ diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 8b5a295752..f942987532 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -30,7 +30,7 @@ position: absolute; padding: 5px 8px; pointer-events: none; - top: 0; + top: 2px; } input[type="text"] { @@ -239,8 +239,8 @@ .list-view-add-layout { margin-top: 10px; - color: @ui-action-disgrete-type; - border: 1px dashed @ui-action-disgrete-border; + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; display: flex; align-items: center; justify-content: center; @@ -250,6 +250,6 @@ .list-view-add-layout:hover { text-decoration: none; - color: @ui-action-disgrete-type-hover; - border-color: @ui-action-disgrete-border-hover; + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 62ddb28041..c666a69166 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -231,13 +231,15 @@ label:not([for]) { align-items: center; } -.controls-row > .vertical-align-items > input.umb-property-editor-tiny { - margin-left: 5px; - margin-right: 5px; +.controls-row > .vertical-align-items > input.umb-property-editor-tiny, +.controls-row > .vertical-align-items > input.umb-property-editor-small { + margin-left: 5px; + margin-right: 5px; } -.controls-row > .vertical-align-items > input.umb-property-editor-tiny:first-child { - margin-left: 0; +.controls-row > .vertical-align-items > input.umb-property-editor-tiny:first-child +.controls-row > .vertical-align-items > input.umb-property-editor-small:first-child { + margin-left: 0; } .thumbnails .selected { diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 771be1bc2a..51f87d09dd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -52,7 +52,17 @@ bottom: 0px; left: 0px; right: 0px; - position: absolute;; + position: absolute; +} + +.--notInFront .umb-modalcolumn::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + background: rgba(0,0,0,.4); } /* re-align loader */ diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 4e7937830c..e36acdc273 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -13,23 +13,25 @@ border: none; border-radius: 0; overflow-y: auto; - background-color: @purple-d2; + background-color: @blueNight; } .login-overlay__background-image { background-position: center center; background-repeat: no-repeat; background-size: cover; + background-image: url('../img/login.jpg'); width: 100%; height: 100%; position: absolute; - opacity: 0.05; } .login-overlay__logo { position: absolute; top: 22px; left: 25px; + width: 30px; + height: 30px; z-index: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index a8c1efe1d3..bad0ab9715 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -346,7 +346,7 @@ justify-content: center; align-items: center; background: @white; - border: 1px solid @ui-action-disgrete-border; + border: 1px solid @ui-action-discreet-border; animation: fadeIn 0.5s; border-radius: 3px; width: 50px; @@ -354,7 +354,7 @@ .icon { opacity: 0.8; } - border-color: @ui-action-disgrete-border-hover; + border-color: @ui-action-discreet-border-hover; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index 916f1b5a3a..e14bb5c0d6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -16,6 +16,75 @@ border-left: 1px solid @gray-10; } +.date-wrapper__date .flatpickr-input > a { + + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + padding: 4px 15px; + box-sizing: border-box; + min-width: 200px; + + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; + border-radius: 3px; + + &:hover, &:focus { + text-decoration: none; + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + + localize { + text-decoration: none; + } + } +} + +//----- VARIANTS SCHEDULED PUBLISH ------ + +.date-wrapper-mini { + display: flex; + flex-direction: row; +} + +.date-wrapper-mini__date { + display: flex; + + margin-left: 5px; + margin-top: 5px; + margin-bottom: 10px; + + &:first-of-type { + margin-left: 0; + } +} + +.date-wrapper-mini__date .flatpickr-input > a { + + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + padding: 1px 15px; + box-sizing: border-box; + min-width: 180px; + + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; + border-radius: 3px; + + &:hover, &:focus { + text-decoration: none; + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + + localize { + text-decoration: none; + } + } +} + //------------------- HISTORY ------------------ .history { @@ -71,4 +140,4 @@ .history-line { display: none; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 307f32788e..9d2c3301fb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -18,7 +18,16 @@ &-push { float:right; - } + } + + &--list{ + float: left; + } + + &__item{ + line-height: 1; + margin: 0 0 5px; + } } .umb-property-editor-tiny { @@ -69,6 +78,11 @@ } } +.umb-property .alert { + border-radius: 3px; +} + + // // Content picker @@ -213,7 +227,7 @@ margin: 24px 0 0; display: flex; } - + &__input { width: 100%; &-wrap{ @@ -255,8 +269,8 @@ transition: all 150ms ease-in-out; &:hover { - color: @ui-action-disgrete-type-hover; - border-color: @ui-action-disgrete-type-hover; + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-type-hover; } } @@ -906,13 +920,6 @@ min-height:200px; } -// -// Nested boolean (e.g. list view bulk action permissions) -// ------------------------------------------------------- -.umb-nested-boolean label {margin-bottom: 8px; float: left; width: 320px;} -.umb-nested-boolean label span {float: left; width: 80%;} -.umb-nested-boolean label input[type='checkbox'] {margin-right: 10px; float: left;} - // // Custom styles of property editors in property preview in document type editor // ----------------------------------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index cf362a67f0..6388369b51 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -37,7 +37,8 @@ ul.sections>li>a::after { height: 4px; width: 100%; background-color: @pinkLight; - position: absolute; + position: absolute; + left: 0; bottom: -4px; border-radius: 3px 3px 0 0; opacity: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 2d32f0e088..55be161cf8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -41,8 +41,8 @@ @purple-washed: #F6F3FD; // UI Colors -@red-d1: #F02E28;// currently used for validation, and is hard coded inline in various html places :/ -@red: #D42054;// updated 2019 - should be used as validation! and is already in some cases like the .umb-validation-label +@red-d1: #F02E28; +@red: #D42054;// updated 2019 @red-l1: #e22c60;// updated 2019 @red-l2: #FE8B88; @red-l3: #FFB2B0; @@ -112,7 +112,7 @@ @blueExtraDark: #1b264f;// added 2019 @blueLight: #ADD8E6; @blueNight: #162335;// added 2019 -@orange: #f79c37;// updated 2019 +//@orange: #f79c37;// updated 2019 @pink: #D93F4C;// #C3325F;// update 2019 @pinkLight: #f5c1bc;// added 2019 @pinkRedLight: #ff8a89;// added 2019 @@ -130,8 +130,12 @@ // ------------------------- @ui-option-type: @blueExtraDark; -@ui-option-hover: @sand-7; @ui-option-type-hover: @blueMid; +@ui-option-hover: @sand-7; + +@ui-option-disabled-type: @gray-6; +@ui-option-disabled-type-hover: @gray-5; +@ui-option-disabled-hover: @sand-7; //@ui-active: #346ab3; @ui-active: @pinkLight; @@ -162,12 +166,12 @@ @ui-action-border: @blueExtraDark; @ui-action-border-hover: @blueMid; -@ui-action-disgrete: white; -@ui-action-disgrete-hover: @sand-7; -@ui-action-disgrete-type: @blueExtraDark; -@ui-action-disgrete-type-hover: @blueMid; -@ui-action-disgrete-border: @gray-7; -@ui-action-disgrete-border-hover: @blueMid; +@ui-action-discreet: white; +@ui-action-discreet-hover: @sand-7; +@ui-action-discreet-type: @blueExtraDark; +@ui-action-discreet-type-hover: @blueMid; +@ui-action-discreet-border: @gray-7; +@ui-action-discreet-border-hover: @blueMid; @type-white: @white; @type-black: @blueNight; @@ -195,6 +199,16 @@ .turquoise{color: @turquoise;} .turquoise-d1{color: @turquoise-d1;} +.text-warning { + color: @orange; +} +.text-error { + color: @red; +} +.text-success { + color: @green; +} + //icon colors for tree icons .color-red, .color-red i{color: @red-d1 !important;} @@ -508,7 +522,7 @@ @warningBorder: transparent; @errorText: @white; -@errorBackground: @red-d1; +@errorBackground: @red; @errorBorder: transparent; @successText: @white; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js index 060e17a55e..83b73b408d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js @@ -78,7 +78,7 @@ // method to select a search result function selectResult(evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); } //callback when there are search results @@ -96,7 +96,7 @@ // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js index 9836b72468..c86f55b255 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js @@ -10,7 +10,7 @@ (function() { "use strict"; - function DataTypePicker($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) { + function DataTypePicker($scope, $filter, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) { var vm = this; @@ -119,13 +119,28 @@ $scope.model.itemDetails = null; if (vm.searchTerm) { - vm.showFilterResult = true; vm.showTabs = false; + + var regex = new RegExp(vm.searchTerm, "i"); + vm.filterResult = { + userConfigured: filterCollection(vm.userConfigured, regex), + typesAndEditors: filterCollection(vm.typesAndEditors, regex) + }; } else { - vm.showFilterResult = false; + vm.filterResult = null; vm.showTabs = true; } + } + function filterCollection(collection, regex) { + return _.map(_.keys(collection), function (key) { + return { + group: key, + dataTypes: $filter('filter')(collection[key], function (dataType) { + return regex.test(dataType.name) || regex.test(dataType.alias); + }) + } + }); } function showDetailsOverlay(property) { @@ -201,4 +216,4 @@ angular.module("umbraco").controller("Umbraco.Editors.DataTypePickerController", DataTypePicker); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html index f3f991c63e..43933f8051 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html @@ -79,13 +79,13 @@ -
+
-
-
-
{{key}}
+
+
+
{{result.group}}
    -
  • @@ -101,11 +101,11 @@
-
-
-
{{key}}
+
+
+
{{result.group}}
    -
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js index 099439fa4b..46a4238c0c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js @@ -10,7 +10,8 @@ (function () { "use strict"; - function DataTypeSettingsController($scope, dataTypeResource, dataTypeHelper, localizationService, notificationsService, overlayService, formHelper) { + function DataTypeSettingsController($scope, dataTypeResource, dataTypeHelper, + localizationService, notificationsService, overlayService, formHelper, eventsService) { var vm = this; @@ -103,27 +104,33 @@ var preValues = dataTypeHelper.createPreValueProps(vm.dataType.preValues); - dataTypeResource.save(vm.dataType, preValues, $scope.model.create).then(function(newDataType) { - $scope.model.dataType = newDataType; - vm.saveButtonState = "success"; + dataTypeResource.save(vm.dataType, preValues, $scope.model.create).then( + function(newDataType) { + $scope.model.dataType = newDataType; + + var args = { dataType: newDataType }; + eventsService.emit("editors.dataTypeSettings.saved", args); + + vm.saveButtonState = "success"; - if ($scope.model && $scope.model.submit) { - $scope.model.submit($scope.model); - } - }, function(err) { - vm.saveButtonState = "error"; - - if(err.status === 400) { - if (err.data && (err.data.ModelState)) { - - formHelper.handleServerValidation(err.data.ModelState); + if ($scope.model && $scope.model.submit) { + $scope.model.submit($scope.model); + } + }, function(err) { + vm.saveButtonState = "error"; + + if(err.status === 400) { + if (err.data && (err.data.ModelState)) { + + formHelper.handleServerValidation(err.data.ModelState); - for (var e in err.data.ModelState) { - notificationsService.error("Validation", err.data.ModelState[e][0]); + for (var e in err.data.ModelState) { + notificationsService.error("Validation", err.data.ModelState[e][0]); + } } } } - }); + ); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html index 620f9f1731..3faf74fdef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html @@ -11,7 +11,7 @@ hide-description="true"> - + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js index c2a66dddf1..7e1e4b9047 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js @@ -79,7 +79,7 @@ // method to select a search result function selectResult(evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); } //callback when there are search results @@ -96,7 +96,7 @@ // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/logviewersearch/logviewersearch.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/logviewersearch/logviewersearch.html index 464a24517a..8ee9d1aa64 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/logviewersearch/logviewersearch.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/logviewersearch/logviewersearch.html @@ -6,6 +6,6 @@

    Name:
    - +

    -
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index 404b4d8dd2..4af8c83983 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -1,5 +1,4 @@
-
@@ -79,7 +78,7 @@
-
+
  • @@ -127,7 +126,7 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index d6ec18667c..d5dc203d67 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -2,10 +2,10 @@
- +
- - -

-
- +
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html index c8039448fd..ded527b3f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html @@ -6,9 +6,9 @@
- -
- + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-notification-list.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-notification-list.html new file mode 100644 index 0000000000..739aab0730 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-notification-list.html @@ -0,0 +1,8 @@ + + + + + {{notification.message}} + + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index 227a08c54f..d3bf14b58c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -38,7 +38,7 @@ {{vm.currentVariant.language.name}} - + {{variant.language.name}} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html index 7d84cf830f..68a3da435b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html @@ -1,13 +1,13 @@ + tabindex="-1" + ng-href="" + ng-click="vm.clicked()" + hotkey="{{::vm.hotkey}}" + hotkey-when-hidden="true" + ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}"> {{ vm.item.name }} -
{{vm.item.badge.count}}
+
{{vm.item.badge.count}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html new file mode 100644 index 0000000000..d40263c6b6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html @@ -0,0 +1,19 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html new file mode 100644 index 0000000000..877e55a1c5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html @@ -0,0 +1,13 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html b/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html index f3b854a062..519c11f76b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html @@ -1,3 +1,3 @@ -
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html index 487be4af87..ce85537d7c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -10,10 +10,13 @@
- +